From 318f8dfb6abd80d3b2cedd6adffb376bad77d1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 10:01:18 +0100 Subject: [PATCH 01/11] flow: write the GDS file with the klayout Python module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit def2stream.py merges the GDS file with the klayout Python module from pip (etc/requirements-common), run by PYTHON_EXE, instead of under the KLayout application (klayout -zz -r def2stream.py). The script already used only klayout.db calls; it gets a command line. Without the module it stops with an error and writes no file. The GDS merge no longer depends on check-klayout. DRC and LVS still run the KLayout application, so they depend on check-klayout directly. The wrapped-macro merge passed `-rd config_file=$(FILL_CONFIG)`, which def2stream.py never read; it is not passed any more. test_def2stream.py checks the module path and the error without it; test_klayout_sh.py checks that klayout.sh still stops without KLayout and passes a call through unchanged with it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018PFLaCzFtFbnWE4nPTDwpC Signed-off-by: Øyvind Harboe --- docs/user/UserGuide.md | 4 +- etc/requirements-common.in | 1 + etc/requirements-common_lock.txt | 43 +++++++++++++++++++ flow/Makefile | 40 +++++++++--------- flow/test/test_def2stream.py | 45 ++++++++++++++++++++ flow/test/test_klayout_sh.py | 72 ++++++++++++++++++++++++++++++++ flow/util/def2stream.py | 62 +++++++++++++++++---------- 7 files changed, 222 insertions(+), 45 deletions(-) create mode 100644 flow/test/test_klayout_sh.py diff --git a/docs/user/UserGuide.md b/docs/user/UserGuide.md index 60ed6611ac..3d451b12f8 100644 --- a/docs/user/UserGuide.md +++ b/docs/user/UserGuide.md @@ -6,8 +6,8 @@ The OpenROAD Project uses three tools to perform automated RTL-to-GDS layout gen Synthesis 2. [OpenROAD](https://github.com/The-OpenROAD-Project/OpenROAD): Floorplanning through Detailed Routing -3. [KLayout](https://www.klayout.de/): GDS merge, DRC and LVS (public - PDKs) +3. [KLayout](https://www.klayout.de/): GDS merge (its Python module), + DRC and LVS (public PDKs, the application) To automate RTL-to-GDS we provide [OpenROAD Flow](https://github.com/The-OpenROAD-Project/OpenROAD-flow-scripts), diff --git a/etc/requirements-common.in b/etc/requirements-common.in index 95a8533659..ab7556786b 100644 --- a/etc/requirements-common.in +++ b/etc/requirements-common.in @@ -2,5 +2,6 @@ pandas numpy google-cloud-pubsub click +klayout pyyaml yamlfix diff --git a/etc/requirements-common_lock.txt b/etc/requirements-common_lock.txt index ac14e895be..18a3dab78e 100644 --- a/etc/requirements-common_lock.txt +++ b/etc/requirements-common_lock.txt @@ -397,6 +397,49 @@ idna==3.17 \ --hash=sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c \ --hash=sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f # via requests +klayout==0.30.12 \ + --hash=sha256:041ed2f9a30700017e560a3481e6f2493a23e1ef40b1c7ed0c635221dde31119 \ + --hash=sha256:05a4b2a7aecb938fe433e4b467fae1a82084e20a504fbe954fe811f7877ac3b1 \ + --hash=sha256:0856279ae47770fea8346c2360049116643e1f6c40cca67e54bbc96b282628d0 \ + --hash=sha256:0c6bb6cd0d8041b7b05148c887a1cbda5d72d7d1988950f9a98670d302dfbb80 \ + --hash=sha256:13a0e1e88be115ee6548cfec37e0a5ea3f4e3b7e87ab09c3de56d6110f4053f4 \ + --hash=sha256:1829dac95a54c6b76b9b007b3f5750151c6af51a86e2e001991f9b40d392b2fd \ + --hash=sha256:1a11690184f9cf2aed3813cc6eecf836668701a2b4449332b75babff4aae751b \ + --hash=sha256:1d62082cc42bcdf17d9831f3c6f6cde5c779597806ab3bcddbc7af720dbaf611 \ + --hash=sha256:1f89f7019c3603f8763eefc2e75f57fc89360acbc6fdb70aa8c4410a14c67bbd \ + --hash=sha256:29ea5574d48119e20dfabf0c343adc073f3a020ea9aba583142c38c0a6a507b6 \ + --hash=sha256:3e193f84cdb5ab0e69c32151c5a9581488c0e5eb5aebffd4a5c07f4b63daea41 \ + --hash=sha256:54c41a6e252c6057fadb9fc1c776f57d9016fee52589cbcc2efe091cd8341fe3 \ + --hash=sha256:59440c1822b0c2374bd8614118d6daef6e48ece84a5024dfc7b0fb416153a995 \ + --hash=sha256:5fc221047e2c57c6666321a5d0120ccb8e71cb0f36210dbe85a7e998aff95ced \ + --hash=sha256:75598cea0cb8525bfa3e6de65ab827d88214da1e1b41ed8c13bec0a66aae75e7 \ + --hash=sha256:7bf6dc89ed3bb1039d75219b4b0a4f6d903613e56c1bfc271c6d4c91595cd46b \ + --hash=sha256:7db06419874bca75beae9a04ef3a6bd8eac8797f9c410ac3f3e7c54be3030604 \ + --hash=sha256:86e36cc0363eab587fa3ac1197137f4e6f835d9cc2e3388ffd4ffa8bcf9c91ea \ + --hash=sha256:8d6e121f63ed121f1f7f6360da4975f902bcb4cade3bd2b0c6315c53b41bc114 \ + --hash=sha256:90f3eed2cecf17f113b3a6a40b0dc537ce521ced4f16e897bf84981bee593847 \ + --hash=sha256:98021838db7da435d017fbaa8e4fbdd07e53a89fa8c810595e8eeb56d21f3b81 \ + --hash=sha256:99e0c5071e476e128e5978a36f1c0b65b666b874c47834f99d47a0e8584df234 \ + --hash=sha256:9ef54933f9f0b12c43e70febbea02bb8fe89abbb33ec93519c9fce94893060f1 \ + --hash=sha256:a0ffea3925b6d9d6eaa576346b45994084bcecd6402459d2505bb4d77795eed5 \ + --hash=sha256:a14507df0725af79dee9562697f1c61e37417ade1a8e48946348f9156690e603 \ + --hash=sha256:a3e5e519d90c4585e71b1153b2d81364b1ef8e85097b98882ca48cc3938a85e5 \ + --hash=sha256:a48d1c41e2561172ef182487a4b9b58fb001ddfff857207cf220b7baf4852004 \ + --hash=sha256:a8574b8348823f21785be10dc15b182cc3a855a6ed192bf15df470102404bb7d \ + --hash=sha256:ac23de4cb6b0fd494d9392a9723a827e0f419614565a3ae14d66128538580765 \ + --hash=sha256:af33e86cf7c7b50f1ce247aee6de37b25913ea28f4d9fc62f33ad314c0edbb03 \ + --hash=sha256:b435290690ac904cfbe90295f701f5cacd8dc01f3f62ed574bb2c2e76f6a65ba \ + --hash=sha256:c3816e142d1baec735da2d65148adcead141a51d278e53739b67af0c3e73e239 \ + --hash=sha256:cafae9930d59ff49831cf4819e5b9feada26420a0e8a22701dc7978368dd7b1f \ + --hash=sha256:ced161406f88193c80355e16870e9ba2e060d0035d1ebb76eeecadf3d0fbb252 \ + --hash=sha256:d2deefc92f87a7caad39a0c99b70fa070c998df48a1146d2ba76b033e3ae5487 \ + --hash=sha256:dc5b20cfccd984c79297dea950d7e2e55bbabb31be201b2d5449d4ac5c3b36cc \ + --hash=sha256:e4d1e8daf731141c431bdf0394b42a298f52f6dd6cc410f30cb9b7cc36c3ae67 \ + --hash=sha256:e7211457f72ec2de518ca4760e49c34775fcdcb6f9866630ce68cb7d7fdd51ec \ + --hash=sha256:eadc239c355b96c6d37143ef9a140718358b72b5f1e8690d45c7c693511b0f15 \ + --hash=sha256:ed0541b60ae4e160723d3d3c23c67c9da0ad6a9e9a28284c1d28fe3f7cb772da \ + --hash=sha256:f4182180e6b8ba25e17c573a1bf38d2a1c51e04586dc8d2315b62357174d5e5f + # via -r etc/requirements-common.in loguru==0.7.3 \ --hash=sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6 \ --hash=sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c diff --git a/flow/Makefile b/flow/Makefile index d2c3970f50..b3eb9fde57 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -756,7 +756,7 @@ clean_abstract: check-klayout: @if [ -z "$(KLAYOUT_CMD)" ]; then \ echo "Error: KLayout not found. Install KLayout or set KLAYOUT_CMD."; \ - echo "Hint: KLayout is needed for GDS/DRC/LVS targets."; \ + echo "Hint: KLayout is needed for DRC, LVS, the gallery and the viewers."; \ exit 1; \ fi @@ -767,32 +767,30 @@ gds: $(GDS_FINAL_FILE) #------------------------------------------------------------------------------- $(WRAPPED_GDSOAS): $(OBJECTS_DIR)/klayout_wrap.lyt $(WRAPPED_LEFS) $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_merge_$(basename $(notdir $@)).log) --tee -- \ - $(SCRIPTS_DIR)/klayout.sh -zz -rd design_name=$(basename $(notdir $@)) \ - -rd in_def=$(OBJECTS_DIR)/def/$(notdir $(@:$(STREAM_SYSTEM_EXT)=def)) \ - -rd in_files="$(ADDITIONAL_GDSOAS)" \ - -rd config_file=$(FILL_CONFIG) \ - -rd seal_file="" \ - -rd out_file=$@ \ - -rd tech_file=$(OBJECTS_DIR)/klayout_wrap.lyt \ - -rd layer_map=$(GDS_LAYER_MAP) \ - -r $(UTILS_DIR)/def2stream.py + $(PYTHON_EXE) $(UTILS_DIR)/def2stream.py \ + --design-name=$(basename $(notdir $@)) \ + --in-def=$(OBJECTS_DIR)/def/$(notdir $(@:$(STREAM_SYSTEM_EXT)=def)) \ + --in-files="$(ADDITIONAL_GDSOAS)" \ + --out-file=$@ \ + --tech-file=$(OBJECTS_DIR)/klayout_wrap.lyt \ + --layer-map=$(GDS_LAYER_MAP) # Merge GDS using Klayout #------------------------------------------------------------------------------- -$(GDS_MERGED_FILE): $(RESULTS_DIR)/6_final.def $(OBJECTS_DIR)/klayout.lyt $(GDSOAS_FILES) $(WRAPPED_GDSOAS) $(SEAL_GDSOAS) | check-klayout +$(GDS_MERGED_FILE): $(RESULTS_DIR)/6_final.def $(OBJECTS_DIR)/klayout.lyt $(GDSOAS_FILES) $(WRAPPED_GDSOAS) $(SEAL_GDSOAS) $(UNSET_AND_MAKE) do-gds-merged .PHONY: do-gds-merged do-gds-merged: $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_1_merge.log) --tee -- \ - $(SCRIPTS_DIR)/klayout.sh -zz -rd design_name=$(DESIGN_NAME) \ - -rd in_def=$(RESULTS_DIR)/6_final.def \ - -rd in_files="$(GDSOAS_FILES) $(WRAPPED_GDSOAS)" \ - -rd seal_file="$(SEAL_GDSOAS)" \ - -rd out_file=$(GDS_MERGED_FILE) \ - -rd tech_file=$(OBJECTS_DIR)/klayout.lyt \ - -rd layer_map=$(GDS_LAYER_MAP) \ - -r $(UTILS_DIR)/def2stream.py + $(PYTHON_EXE) $(UTILS_DIR)/def2stream.py \ + --design-name=$(DESIGN_NAME) \ + --in-def=$(RESULTS_DIR)/6_final.def \ + --in-files="$(GDSOAS_FILES) $(WRAPPED_GDSOAS)" \ + --seal-file="$(SEAL_GDSOAS)" \ + --out-file=$(GDS_MERGED_FILE) \ + --tech-file=$(OBJECTS_DIR)/klayout.lyt \ + --layer-map=$(GDS_LAYER_MAP) $(RESULTS_DIR)/6_final.v: $(LOG_DIR)/6_report.log @@ -807,7 +805,7 @@ $(GDS_FINAL_FILE): $(GDS_MERGED_FILE) .PHONY: drc drc: $(REPORTS_DIR)/6_drc.lyrdb -$(REPORTS_DIR)/6_drc.lyrdb: $(GDS_FINAL_FILE) $(KLAYOUT_DRC_FILE) +$(REPORTS_DIR)/6_drc.lyrdb: $(GDS_FINAL_FILE) $(KLAYOUT_DRC_FILE) | check-klayout ifneq ($(KLAYOUT_DRC_FILE),) $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_drc.log) --tee -- \ $(SCRIPTS_DIR)/klayout.sh -zz -rd in_gds="$<" \ @@ -828,7 +826,7 @@ $(OBJECTS_DIR)/6_final_concat.cdl: $(RESULTS_DIR)/6_final.cdl $(CDL_FILE) .PHONY: lvs lvs: $(RESULTS_DIR)/6_lvs.lvsdb -$(RESULTS_DIR)/6_lvs.lvsdb: $(GDS_FINAL_FILE) $(KLAYOUT_LVS_FILE) $(OBJECTS_DIR)/6_final_concat.cdl +$(RESULTS_DIR)/6_lvs.lvsdb: $(GDS_FINAL_FILE) $(KLAYOUT_LVS_FILE) $(OBJECTS_DIR)/6_final_concat.cdl | check-klayout ifneq ($(KLAYOUT_LVS_FILE),) $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_lvs.log) --tee -- \ $(SCRIPTS_DIR)/klayout.sh -b -rd in_gds="$<" \ diff --git a/flow/test/test_def2stream.py b/flow/test/test_def2stream.py index cddfc7951d..be48130f70 100644 --- a/flow/test/test_def2stream.py +++ b/flow/test/test_def2stream.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import io import unittest from unittest.mock import MagicMock, patch, call import sys @@ -539,5 +540,49 @@ def test_multiple_gds_files_read(self): self.assertEqual(len(read_calls), 4) # 1 DEF + 3 GDS +class TestMain(unittest.TestCase): + """The stream file is written by the klayout Python module, and + without it the merge stops with an error.""" + + def setUp(self): + self.tmp_dir = tempfile.TemporaryDirectory() + self.out_file = os.path.join(self.tmp_dir.name, "6_1_merged.gds") + self.argv = [ + "--design-name=gcd", + "--in-def=6_final.def", + "--in-files=a.gds b.gds", + f"--out-file={self.out_file}", + "--tech-file=klayout.lyt", + ] + + def tearDown(self): + self.tmp_dir.cleanup() + + def test_python_module_merges(self): + pya_mod = MagicMock() + with patch.object(def2stream, "pya", pya_mod), patch.object( + def2stream, "merge_gds", return_value=0 + ) as merge_gds: + self.assertEqual(def2stream.main(self.argv, environ={}), 0) + kwargs = merge_gds.call_args.kwargs + self.assertIs(kwargs["pya_mod"], pya_mod) + self.assertEqual(kwargs["design_name"], "gcd") + self.assertEqual(kwargs["in_files"], "a.gds b.gds") + self.assertEqual(kwargs["out_file"], self.out_file) + self.assertEqual(kwargs["seal_file"], "") + self.assertEqual(kwargs["layer_map"], "") + + def test_without_the_python_module_is_an_error(self): + with patch.object(def2stream, "pya", None), patch( + "sys.stdout", new_callable=io.StringIO + ) as stdout: + self.assertEqual(def2stream.main(self.argv, environ={}), 1) + self.assertIn( + "ERROR: the klayout Python module is not installed, pip install klayout", + stdout.getvalue(), + ) + self.assertFalse(os.path.exists(self.out_file)) + + if __name__ == "__main__": unittest.main() diff --git a/flow/test/test_klayout_sh.py b/flow/test/test_klayout_sh.py new file mode 100644 index 0000000000..2bf841f1d1 --- /dev/null +++ b/flow/test/test_klayout_sh.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""klayout.sh runs a KLayout executable: DRC, LVS, the gallery and the +viewers. Without KLayout it stops with an error. With KLayout, the call +goes to it unchanged. +""" + +import os +import subprocess +import tempfile +import unittest + +SCRIPTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "scripts") +KLAYOUT_SH = os.path.join(SCRIPTS_DIR, "klayout.sh") + + +class TestKlayoutSh(unittest.TestCase): + def setUp(self): + self.tmp_dir = tempfile.TemporaryDirectory() + self.out_file = os.path.join(self.tmp_dir.name, "6_1_merged.gds") + + def tearDown(self): + self.tmp_dir.cleanup() + + def run_klayout(self, klayout_cmd, *args): + return subprocess.run( + ["bash", KLAYOUT_SH, *args], + env=dict(os.environ, KLAYOUT_CMD=klayout_cmd), + capture_output=True, + text=True, + ) + + def stream_args(self): + return [ + "-zz", + "-rd", + "design_name=gcd", + "-rd", + f"out_file={self.out_file}", + "-r", + "/flow/util/def2stream.py", + ] + + def test_drc_still_needs_klayout(self): + result = self.run_klayout( + "", + "-zz", + "-rd", + "in_gds=6_final.gds", + "-rd", + "report_file=6_drc.lyrdb", + "-r", + "/flow/platforms/asap7/drc/asap7.lydrc", + ) + self.assertEqual(result.returncode, 1) + self.assertIn("Error: KLayout not found", result.stdout) + + def test_viewer_still_needs_klayout(self): + result = self.run_klayout("", "-nn", "klayout.lyt", "6_final.odb") + self.assertEqual(result.returncode, 1) + self.assertIn("Error: KLayout not found", result.stdout) + self.assertFalse(os.path.exists(self.out_file)) + + def test_installed_klayout_gets_the_call(self): + result = self.run_klayout("echo", *self.stream_args()) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertNotIn("WARNING", result.stdout) + self.assertIn(f"out_file={self.out_file}", result.stdout) + self.assertFalse(os.path.exists(self.out_file)) + + +if __name__ == "__main__": + unittest.main() diff --git a/flow/util/def2stream.py b/flow/util/def2stream.py index 41f55800d3..6058418113 100644 --- a/flow/util/def2stream.py +++ b/flow/util/def2stream.py @@ -3,9 +3,10 @@ except ImportError: pya = None +import argparse +import os import re import sys -import os def merge_gds( @@ -158,25 +159,42 @@ def auto_memories_cells(environ): return frozenset(line.split()[0] for line in f if line.strip()) -# When run via klayout -r, globals tech_file, layer_map, in_def, etc. -# are set by klayout's -rd mechanism. -if pya is not None: - try: - # These globals are set by klayout -rd flags - sys.exit( - merge_gds( - pya_mod=pya, - tech_file=tech_file, # noqa: F821 - set by klayout -rd - layer_map=layer_map, # noqa: F821 - in_def=in_def, # noqa: F821 - design_name=design_name, # noqa: F821 - in_files=in_files, # noqa: F821 - seal_file=seal_file, # noqa: F821 - out_file=out_file, # noqa: F821 - allow_empty=os.environ.get("GDS_ALLOW_EMPTY", ""), - allow_empty_cells=auto_memories_cells(os.environ), - ) +def parse_args(argv): + parser = argparse.ArgumentParser( + description="Merge a DEF file and GDS/OAS files into one stream file." + ) + parser.add_argument("--design-name", required=True) + parser.add_argument("--in-def", required=True) + parser.add_argument("--in-files", default="") + parser.add_argument("--seal-file", default="") + parser.add_argument("--out-file", required=True) + parser.add_argument("--tech-file", required=True) + parser.add_argument("--layer-map", default="") + return parser.parse_args(argv) + + +def main(argv, environ=os.environ): + """Write the stream file with the klayout Python module.""" + args = parse_args(argv) + if pya is None: + print( + "ERROR: the klayout Python module is not installed, pip install" + " klayout (etc/requirements-common_lock.txt)" ) - except NameError: - # Not running under klayout -r, pya available but no -rd globals - pass + return 1 + return merge_gds( + pya_mod=pya, + tech_file=args.tech_file, + layer_map=args.layer_map, + in_def=args.in_def, + design_name=args.design_name, + in_files=args.in_files, + seal_file=args.seal_file, + out_file=args.out_file, + allow_empty=environ.get("GDS_ALLOW_EMPTY", ""), + allow_empty_cells=auto_memories_cells(environ), + ) + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) From 52802959c460ae24a2a62047707425edb4b0ce55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 08:16:13 +0100 Subject: [PATCH 02/11] asap7: DRC deck in Python on the klayout module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, make drc on asap7 needed the KLayout application to run the Ruby deck asap7.lydrc. Now it runs asap7_drc.py with the klayout Python module that already writes the GDS. flow/util/klayout_drc.py is the thin layer KLayout's DRC language is over klayout.db (KLayout/klayout#1980), for the operations ORFS's decks use: each method calls Region, Edges or EdgePairs with the arguments and defaults of _drc_layer.rb, values in microns as floats and in database units as ints, deep mode on a DeepShapeStore, and the report written with rdb's scan_collection into the .lyrdb that make drc counts. The deck is the Ruby deck translated line by line. make drc runs a deck ending in .py with PYTHON_EXE; a .lydrc deck still goes to the KLayout application. RBA::CellView::active, which only the application has, is gone with the Ruby deck: the report file is always given. Checked against the Ruby deck run by the KLayout 0.30.0 application on the same asap7/gcd GDS, Python on klayout 0.30.13: clean GDS: 42 violations in 9 categories, both, same markers seeded GDS: 64 violations in 17 categories, both, same markers (narrow M1/M2, V1 spacing and enclosure, GATE width, ACTIVE without select) runtime: 73 s Ruby, 55 s Python Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- flow/Makefile | 9 +- flow/docs/KLayoutOptionalDependency.md | 59 - flow/platforms/asap7/config.mk | 2 +- flow/platforms/asap7/drc/asap7.lydrc | 561 --------- flow/platforms/asap7/drc/asap7_drc.py | 1467 ++++++++++++++++++++++++ flow/test/test_klayout_drc.py | 111 ++ flow/test/test_klayout_sh.py | 12 +- flow/util/klayout_drc.py | 733 ++++++++++++ 8 files changed, 2325 insertions(+), 629 deletions(-) delete mode 100644 flow/docs/KLayoutOptionalDependency.md delete mode 100644 flow/platforms/asap7/drc/asap7.lydrc create mode 100644 flow/platforms/asap7/drc/asap7_drc.py create mode 100644 flow/test/test_klayout_drc.py create mode 100644 flow/util/klayout_drc.py diff --git a/flow/Makefile b/flow/Makefile index b3eb9fde57..3c0ed8e58d 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -805,12 +805,17 @@ $(GDS_FINAL_FILE): $(GDS_MERGED_FILE) .PHONY: drc drc: $(REPORTS_DIR)/6_drc.lyrdb -$(REPORTS_DIR)/6_drc.lyrdb: $(GDS_FINAL_FILE) $(KLAYOUT_DRC_FILE) | check-klayout +$(REPORTS_DIR)/6_drc.lyrdb: $(GDS_FINAL_FILE) $(KLAYOUT_DRC_FILE) $(if $(filter %.py,$(KLAYOUT_DRC_FILE)),$(UTILS_DIR)/klayout_drc.py,| check-klayout) ifneq ($(KLAYOUT_DRC_FILE),) $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_drc.log) --tee -- \ + $(if $(filter %.py,$(KLAYOUT_DRC_FILE)), \ + $(PYTHON_EXE) $(UTILS_DIR)/klayout_drc.py \ + --deck=$(KLAYOUT_DRC_FILE) \ + --in-gds=$< \ + --report-file=$(abspath $@), \ $(SCRIPTS_DIR)/klayout.sh -zz -rd in_gds="$<" \ -rd report_file=$(abspath $@) \ - -r $(KLAYOUT_DRC_FILE) + -r $(KLAYOUT_DRC_FILE)) # Hacky way of getting DRV count (don't error on no matches) grep -c "" $@ > $(REPORTS_DIR)/6_drc_count.rpt || [[ $$? == 1 ]] else diff --git a/flow/docs/KLayoutOptionalDependency.md b/flow/docs/KLayoutOptionalDependency.md deleted file mode 100644 index c469160a27..0000000000 --- a/flow/docs/KLayoutOptionalDependency.md +++ /dev/null @@ -1,59 +0,0 @@ -# KLayout as an Optional Dependency - -KLayout is only required for GDS/OAS stream generation, DRC, and LVS -verification. All other ORFS functionality — synthesis, floorplanning, -placement, CTS, routing, timing reports, and abstract generation — works -without KLayout installed. - -## Makefile Targets - -| Target | Requires KLayout | Description | -|---|---|---| -| `make finish` | Yes | Complete flow including GDS generation | -| `make gds` | Yes | Generate GDS/OAS from finished design | -| `make drc` | Yes | Run DRC checks (requires GDS) | -| `make lvs` | Yes | Run LVS checks (requires GDS) | -| `make gallery` | Yes | Generate layout screenshots | -| `make klayout_` | Yes | Open result in KLayout viewer | -| `make generate_abstract` | No | Generate LEF/LIB abstracts | - -A `check-klayout` guard produces a clear error message when KLayout is -missing and a KLayout-dependent target is invoked: - -``` -Error: KLayout not found. Install KLayout or set KLAYOUT_CMD. -Hint: KLayout is needed for GDS/DRC/LVS targets. -``` - -## bazel-orfs Integration - -bazel-orfs uses the `do-` prefixed targets which bypass Make's dependency -management. `do-finish` / `do-final` only run the finish stage recipe -itself, while `make finish` also pulls in the GDS target as a Make -dependency. `do-gds` runs GDS generation separately (requires KLayout). - -An `orfs_gds()` Bazel rule can call `do-gds` independently from -`orfs_flow()`, making KLayout an optional toolchain dependency configured -in `MODULE.bazel`. - -## KLayout Tech File Generation - -The `do-klayout` and `do-klayout_wrap` targets generate `.lyt` technology -files by substituting LEF and map file paths into platform templates. -This is implemented in `util/generate_klayout_tech.py` using stdlib XML -processing — no KLayout dependency required. - -## Testing Without KLayout - -Unit tests for all KLayout-related Python scripts use `unittest.mock` to -mock the `pya` API: - -``` -cd flow/test -python -m unittest test_generate_klayout_tech test_def2stream test_convertDrc -``` - -These tests cover: -- `.lyt` tech file generation (`test_generate_klayout_tech.py`) -- DEF-to-GDS merging logic (`test_def2stream.py`) -- DRC report conversion (`test_convertDrc.py`) diff --git a/flow/platforms/asap7/config.mk b/flow/platforms/asap7/config.mk index a732b7abf6..ce4b31ad96 100644 --- a/flow/platforms/asap7/config.mk +++ b/flow/platforms/asap7/config.mk @@ -74,7 +74,7 @@ export FASTROUTE_TCL ?= $(PLATFORM_DIR)/fastroute.tcl export KLAYOUT_TECH_FILE = $(PLATFORM_DIR)/KLayout/asap7.lyt # KLayout DRC ruledeck -export KLAYOUT_DRC_FILE = $(PLATFORM_DIR)/drc/asap7.lydrc +export KLAYOUT_DRC_FILE ?= $(PLATFORM_DIR)/drc/asap7_drc.py # OpenRCX extRules export RCX_RULES = $(PLATFORM_DIR)/rcx_patterns.rules diff --git a/flow/platforms/asap7/drc/asap7.lydrc b/flow/platforms/asap7/drc/asap7.lydrc deleted file mode 100644 index 47bf0b8a7a..0000000000 --- a/flow/platforms/asap7/drc/asap7.lydrc +++ /dev/null @@ -1,561 +0,0 @@ - - - - - drc - - - - false - false - 0 - - true - drc_scripts - tools_menu.drc.end - dsl - drc-dsl-xml - -########################################################################################## -# BSD 2-Clause License -# -# Copyright (c) 2021, laurentc2 -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -########################################################################################## -# -# Based on work by @laurentc2 found on https://github.com/laurentc2/ASAP7_for_KLayout -# -# DRC for ASAP7 : according to : asap7_drm_201207a.pdf -# found on : https://github.com/The-OpenROAD-Project/asap7/blob/master/asap7PDK_r1p7/docs/asap7_drm_201207a.pdf -# -########################################################################################## - -if $in_gds - source($in_gds) -end - -if $report_file - report("ASAP7 DRC runset", $report_file) -else - report("ASAP7 DRC runset", File.join(File.dirname(RBA::CellView::active.filename), "6_drc_count.rpt")) -end - -# DRC test to run or not -############### -OFFGRID = false -DEBUG = true # manufacturing grid/angle checks - -# KLAYOUT setup -######################## -# Use a tile size of 1mm -tiles(1000.um) -# Use a tile border of 10 micron: -tile_borders(1.um) -#no_borders - -# Hierachical -deep - -# Use 4 CPU cores -threads(8) -verbose(true) - -# layers definitions -######################## -nwell = input(1, 0) -fin = input(2, 0) -gate = input(7, 0) -gcut = input(10, 0) -active = input(11, 0) -sdt = input(88, 0) -nselect = input(12, 0) -pselect = input(13, 0) -slvt = input(97, 0) -lvt = input(98, 0) -sramdrc = input(99, 0) -sramvt = input(110, 0) -dummy = input(8, 0) -lig = input(16, 0) -lisd = input(17, 0) -v0 = input(18, 0) -m1 = input(19, 0) -v1 = input(21, 0) -m2 = input(20, 0) -v2 = input(25, 0) -m3 = input(30, 0) -v3 = input(35, 0) -m4 = input(40, 0) -v4 = input(45, 0) -m5 = input(50, 0) -v5 = input(55, 0) -m6 = input(60, 0) -v6 = input(65, 0) -m7 = input(70, 0) -v7 = input(75, 0) -m8 = input(80, 0) -v8 = input(85, 0) -m9 = input(90, 0) -v9 = input(95, 0) -pad = input(96, 0) - - -# splits a layer classes with increasing min dimensions -def classify_by_width(layer, *dimensions) - dimensions.collect { |d| layer = layer.sized(-0.5 * (d - 1.dbu)).sized(0.5 * (d - 1.dbu)) } -end -# Define a new custom function that selects polygons by their number of holes: -# It will return a new layer containing those polygons with min to max holes. -# max can be nil to omit the upper limit. -class DRC::DRCLayer - def with_holes(min, max) - new_data = RBA::Region::new - self.data.each do |p| - if p.holes >= (min || 0) && (!max || p.holes <= max) - new_data.insert(p) - end - end - DRC::DRCLayer::new(@engine, new_data) - end -end - -# DRC section -######################## -info("DRC section") - -# construction layers : -gatenogcut = gate - gcut -(activesram , activenosram) = active.andnot(sramdrc) -(ligsram , lignosram) = lig.andnot(sramdrc) - -### WELL -nwell.width(108.nm, projection).with_angle(90).output("WELL.W.1", "WELL.W.1 : Min. horizontal width of WELL : 108nm") -nwell.width(54.nm, projection).with_angle(0).output("WELL.W.2", "WELL.W.2 : Min. vertical width of WELL : 54nm") -nwell.space(108.nm, projection).with_angle(0).output("WELL.S.1", "WELL.S.1 : Min. vertical spacing between WELL : 108nm") -nwell.space(54.nm, projection).with_angle(90).output("WELL.S.2", "WELL.S.2 : Min. horizontal spacing between WELL : 54nm") -nwell.with_area(0 .. 0.005832).output("WELL.A.1A", "WELL.A.1A : Min. area of WELL : 5832nm2") -nwell.holes.with_area(0 .. 0.005832).output("WELL.A.1B", "WELL.A.1B : Min. enclosed area of WELL : 5832nm2") -nwell.enclosing(gatenogcut, 7.nm, projection).with_angle(90).output("WELL.GATE.EX.1", "WELL.GATE.EX.1 : Min. horizontal extension of WELL past GATE (not cut by GCUT layer): 7nm") -nwell.not(sramdrc).enclosing(gatenogcut, 7.nm, projection).with_angle(0).output("WELL.GATE.EX.2", "WELL.GATE.EX.2 : Min. vertical extension of WELL past GATE (not cut by GCUT layer): 7nm") - -### FIN -fin.edges.with_angle(90).without_length(7.nm).output("FIN.W.1a", "FIN.W.1 : Exact vertical width of FIN : 7nm") -fin.without_bbox_height(7.nm).output("FIN.W.1", "FIN.W.1 : Exact vertical width of FIN : 7nm") -#fin.drc(if_all(angle == 90, length != 7.nm)).output("FIN.W.1c", "FIN.W.1 : Exact vertical width of FIN : 7nm") -fin.drc(width(projection) < 108.nm).with_angle(90).output("FIN.W.2", "FIN.W.2 : Min. horizontal width of WELL : 108nm") -fin.space(35.nm, projection).polygons.not(fin).without_bbox_height(20.nm).sized(0,3.5.nm).output("FIN.S.1", "FIN.S.1 : Exact vertical FIN pitch : 27nm") -fin.corners(0..90).sized(1.nm).output("FIN.AUX.1", "FIN.AUX.1 : FIN may not bend") - -### GATE -gate.drc(width(projection) != 20.nm).with_angle(90).output("GATE.W.1", "GATE.W.1 : Exact horizontal width of GATE : 20nm") -gate.width(40.nm, projection).with_angle(0).output("GATE.W.2", "GATE.W.2 : Min. vertical width of GATE : 40nm") -gate.space(100.nm, projection).polygons.not(fin).without_bbox_width(34.nm).sized(0,10.nm).output("GATE.S.1", "GATE.S.1 : Exact horizontal GATE pitch : 54nm") -gate.space(34.nm, projection).with_angle(90).output("GATE.S.3", "GATE.S.3 : Min. horizontal spacing between GATE : 34nm") -gatenogcut.not(sramdrc).sized(55.nm,0).not_interacting(gate).output("GATE.S.2", "GATE.S.2 : Every GATE (not cut by GCUT and not interacting with the layer SRAMDRC) must have at least one other GATE within 54 nm of its surrounding along the horizontal axis : 54nm") -gate.corners(0..90).sized(1.nm).output("GATE.AUX.1", "GATE.AUX.1 : GATE may not bend") -gate.sized(1.dbu,1.mm).and(gate).with_holes(2,nil).output("GATE.AUX.2", "GATE.AUX.2 : GATE may not be discontinuous along the vertical axis") -active.edges.with_angle(90).and(gate).output("GATE.AUX.3", "GATE.AUX.3 : ACTIVE layer vertical edge may not lie inside, or coincide with, the GATE layer") -gate.enclosing(gate.and(active), 4.nm, projection).polygons.without_area(0).output("GATE.ACTIVE.EX.1", "GATE.ACTIVE.EX.1 : Min. vertical extension of GATE (not cut by GCUT) past ACTIVE : 4nm") -active.interacting(gate).enclosing(gate.and(active), 25.nm, projection).polygons.without_area(0).output("GATE.ACTIVE.EX.2", "GATE.ACTIVE.EX.2 : Min. horizontal extension of ACTIVE interacting with GATE (not cut by GCUT layer) past GATE (not cut by GCUT layer) : 20nm") -gatenogcut.separation(active, 9.nm, projection).polygons.without_area(0).not_interacting(gate & active).output("GATE.ACTIVE.S.4", "GATE.ACTIVE.S.4 : Min. horizontal spacing between ACTIVE and GATE (not cut by GCUT and not interacting with ACTIVE) : 9nm") - -### ACTIVE -active.enclosing(fin,10.nm, projection).polygons.without_area(0).output("ACTIVE.FIN.EX.1", "ACTIVE.FIN.EX.1 : Min. vertical extension of ACTIVE past FIN : 10nm") -active.width(27.nm, projection).with_angle(0).output("ACTIVE.W.1", "ACTIVE.W.1 : Min. vertical width of ACTIVE : 27nm") -active.without_bbox_height(27.nm).and(active.without_bbox_height(2*27.nm)).and(active.without_bbox_height(3*27.nm)).and(active.without_bbox_height(4*27.nm)).and(active.without_bbox_height(5*27.nm)).and(active.without_bbox_height(6*27.nm)).and(active.without_bbox_height(7*27.nm)).and(active.without_bbox_height(8*27.nm)).and(active.without_bbox_height(9*27.nm)).and(active.without_bbox_height(10*27.nm)).and(active.without_bbox_height(11*27.nm)).and(active.without_bbox_height(12*27.nm)).output("ACTIVE.W.2" , "ACTIVE.W.2 : ACTIVE layer vertical width increment is an integer multiple of : 27nm") -active.width(16.nm, projection).with_angle(90).output("ACTIVE.W.3", "ACTIVE.W.3 : Min. horizontal width of ACTIVE : 16nm") -active.space(27.nm, projection).with_angle(0).output("ACTIVE.S.1", "ACTIVE.S.1 : Min. vertical spacing between ACTIVE : 27nm") -active.interacting(gate).space(92.nm, projection).edges.with_angle(90).extended_out(30.nm).not(lisd).output("ACTIVE.S.2A", "ACTIVE.S.2A : Min. horizontal spacing between ACTIVE layers forming source/drain regions (of different transistors) : 92nm") -active.space(38.nm, projection).with_angle(90).output("ACTIVE.S.2B", "ACTIVE.S.2B : Min. horizontal spacing between ACTIVE : 38nm") -activenosram.separation(nwell, 27.nm, projection).output("ACTIVE.WELL.S.4", "ACTIVE.WELL.S.4 : Min. spacing of ACTIVE to WELL : 27nm") -activesram.separation(nwell, 13.5.nm, projection).output("SRAM.ACTIVE.WELL.S.5", "SRAM.ACTIVE.WELL.S.5 : Min. spacing of ACTIVE in SRAM to WELL : 13.5nm") -nwell.enclosing(activenosram, 27.nm, projection).polygons.without_area(0).output("ACTIVE.WELL.EN.1", "ACTIVE.WELL.EN.1 : Min. enclosure of ACTIVE by WELL : 27nm") -nwell.enclosing(activesram, 13.5.nm, projection).polygons.without_area(0).output("SRAM.ACTIVE.WELL.EN.2", "SRAM.ACTIVE.WELL.EN.2 : Min. enclosure of ACTIVE in SRAM by WELL : 13.5nm") -activenosram.with_area(0 .. 0.000864).output("ACTIVE.A.1A", "ACTIVE.A.1A : Min. area of ACTIVE : 864nm2") -activenosram.holes.with_area(0 .. 0.000864).output("ACTIVE.A.1B", "ACTIVE.A.1B : Min. enclosed area of ACTIVE : 864nm2") -activesram.with_area(0 .. 0.000432).output("SRAM.ACTIVE.A.1A", "SRAM.ACTIVE.A.1A : Min. area of ACTIVE in SRAM : 432nm2") -activesram.holes.with_area(0 .. 0.000432).output("SRAM.ACTIVE.A.1B", "SRAM.ACTIVE.A.1B : Min. enclosed area of ACTIVE in SRAM : 432nm2") -active.not(nselect + pselect).output("ACTIVE.AUX.1", "ACTIVE.AUX.1 : ACTIVE must always be enclosed by NSELECT or PSELECT") -active.edges.and((nselect + pselect).edges).output("ACTIVE.AUX.1_edges", "ACTIVE.AUX.1 : ACTIVE must always be enclosed by NSELECT or PSELECT, such that no ACTIVE layer edge(s) coincide with NSELECT or PSELECT layer edge(s)") -sramdrc.edges.and(active).output("SRAM.ACTIVE.AUX.2", "SRAM.ACTIVE.AUX.2 : ACTIVE not belonging to an SRAM cell may not touch the SRAMDRC layer") -active.notch(1.mm, projection).with_angle(0).output("ACTIVE.AUX.3", "ACTIVE.AUX.3 : notch in ACTIVE along vertical axis is not allowed") -(active.interacting(gate) & nselect).sized(30.um).not_interacting(active & pselect - nwell).size(-30.um).output("ACTIVE.LUP.1" , "ACTIVE.LUP.1 : Maximum distance, to prevent latch-up, between ACTIVE forming a MOS device and ACTIVE forming a bulk/substrate contact within the same WELL/substrate : 30um") -(active.interacting(gate) & pselect).sized(30.um).not_interacting(active & nselect & nwell).size(-30.um).output("ACTIVE.LUP.1" , "ACTIVE.LUP.1 : Maximum distance, to prevent latch-up, between ACTIVE forming a MOS device and ACTIVE forming a bulk/substrate contact within the same WELL/substrate : 30um") - -### GCUT -gcut.width(17.nm, projection).with_angle(0).output("GCUT.W.1", "GCUT.W.1 : Min. vertical width of GCUT : 17nm") -gcut.separation(active, 4.nm, projection).output("GCUT.ACTIVE.S.1", "GCUT.ACTIVE.S.1 : Min. vertical spacing between GCUT and channel : 4nm") -gcut.enclosing(gate.and(gcut), 17.nm, projection).with_angle(90).polygons.without_area(0).output("GCUT.GATE.EX.1", "GCUT.GATE.EX.1 : Min. horizontal extension of GCUT past GATE : 17nm") -gcut.separation(gate, 17.nm, projection).output("GCUT.GATE.S.2", "GCUT.GATE.S.2 : Minimum spacing of GCUT to GATE : 17nm") -gcut.space(35.nm, projection).with_angle(0).output("GCUT.S.3", "GCUT.S.3 : Min. vertical spacing between GCUT : 35nm") -gcut.not_interacting(gate).output("GCUT.AUX.1", "GCUT.AUX.1 : GCUT layer may not exist without the layer GATE") -gcut.edges.with_angle(90).and(gate).output("GCUT.AUX.2", "GCUT.AUX.2 : GCUT layer vertical edge may not lie inside, or coincide with, the GATE layer") -gcut.edges.and(active).output("GCUT.AUX.3", "GCUT.AUX.3 : GCUT may not interact with channel") - -### NSELECT/PSELECT LVT/SLVT/SRAMVT -nselect.width(108.nm, projection).with_angle(90).output("NSELECT.W.1", "NSELECT.W.1 : Min. horizontal width of NSELECT : 108nm") -nselect.width(54.nm, projection).with_angle(0).output("NSELECT.W.2", "NSELECT.W.2 : Min. vertical width of NSELECT : 54nm") -nselect.enclosing(activenosram, 46.nm, projection).with_angle(90).output("NSELECT.ACTIVE.EN.1", "NSELECT.ACTIVE.EN.1 : Min. horizontal enclosure of ACTIVE by NSELECT : 46nm") -nselect.enclosing(activenosram, 27.nm, projection).with_angle(0).output("NSELECT.ACTIVE.EN.2", "NSELECT.ACTIVE.EN.2 : Min. vertical enclosure of ACTIVE by NSELECT : 27nm") -nselect.enclosing(activesram, 13.5.nm, projection).output("SRAM.NSELECT.ACTIVE.EN.3-4", "SRAM.NSELECT.ACTIVE.EN.3-4 : Min. enclosure of ACTIVE in SRAM by NSELECT : 13.5nm") -nselect.enclosing(gatenogcut, 7.nm, projection).with_angle(90).polygons.without_area(0).output("NSELECT.GATE.EX.1-2", "NSELECT.GATE.EX.1-2 : Min. horizontal extension of NSELECT past GATE : 7nm") -pselect.width(108.nm, projection).with_angle(90).output("PSELECT.W.1", "PSELECT.W.1 : Min. horizontal width of PSELECT : 108nm") -pselect.width(54.nm, projection).with_angle(0).output("PSELECT.W.2", "PSELECT.W.2 : Min. vertical width of PSELECT : 54nm") -pselect.enclosing(activenosram, 46.nm, projection).with_angle(90).output("PSELECT.ACTIVE.EN.1", "PSELECT.ACTIVE.EN.1 : Min. horizontal enclosure of ACTIVE by PSELECT : 46nm") -pselect.enclosing(activenosram, 27.nm, projection).with_angle(0).output("PSELECT.ACTIVE.EN.2", "PSELECT.ACTIVE.EN.2 : Min. vertical enclosure of ACTIVE by PSELECT : 27nm") -pselect.enclosing(activesram, 13.5.nm, projection).output("SRAM.PSELECT.ACTIVE.EN.3-4", "SRAM.PSELECT.ACTIVE.EN.3-4 : Min. enclosure of ACTIVE in SRAM by PSELECT : 13.5nm") -pselect.enclosing(gatenogcut, 7.nm, projection).with_angle(90).polygons.without_area(0).output("PSELECT.GATE.EX.1-2", "PSELECT.GATE.EX.1-2 : Min. horizontal extension of PSELECT past GATE : 7nm") -(nselect & pselect).output("NSELECT.PSELECT.AUX.1", "NSELECT.PSELECT.AUX.1 : NSELECT and PSELECT may not overlap") -((lvt & slvt) + (lvt & sramvt) +(slvt & sramvt)).output("VT.AUX.2", "VT.AUX.2 : VT layers (LVT, SLVT, and SRAMVT) may not overlap") - -### SDT -sdt.width(24.nm, projection).with_angle(90).output("SDT.W.1", "SDT.W.1 : Min. horizontal width of SDT : 24nm") -sdt.not(sramdrc).width(27.nm, projection).with_angle(0).output("SDT.W.2", "SDT.W.2 : Min. vertical width of SDT : 27nm") -sdt.without_bbox_height(27.nm).and(sdt.without_bbox_height(2*27.nm)).and(sdt.without_bbox_height(3*27.nm)).and(sdt.without_bbox_height(4*27.nm)).and(sdt.without_bbox_height(5*27.nm)).and(sdt.without_bbox_height(6*27.nm)).and(sdt.without_bbox_height(7*27.nm)).and(sdt.without_bbox_height(8*27.nm)).and(sdt.without_bbox_height(9*27.nm)).and(sdt.without_bbox_height(10*27.nm)).and(sdt.without_bbox_height(11*27.nm)).and(sdt.without_bbox_height(12*27.nm)).output("SDT.W.3" , "SDT.W.3 : SDT vertical height increment is an integer multiple of : 27nm") -sdt.and(sramdrc).width(17.nm, projection).with_angle(0).output("SRAM.SDT.W.4", "SRAM.SDT.W.4 : Min. vertical width of SDT in SRAM : 17nm") -sdt.space(30.nm, projection).with_angle(90).output("SDT.S.1", "SDT.S.1 : Min. vertical spacing between SDT : 30nm") -sdt.separation(gate, 5.nm, projection).output("SDT.GATE.S.2", "SDT.GATE.S.2 : Min. horizontal spacing between SDT and GATE : 5nm") - -### LISD -lisd.width(24.nm).output("LISD.W.1", "LISD.W.1 : Min. width of LISD : 24nm") -lisd.space(18.nm).polygons.not_interacting(lisd.edges.with_length(0..36.nm)).output("LISD.S.1", "LISD.S.1 : Min. spacing between two LISD' edges, when both edges are > 36 nm : 18nm") -lisd.space(25.nm).polygons.not_interacting(lisd.edges.with_length(36.nm..100.mm)).output("LISD.S.2", "LISD.S.2 : Min. spacing between two LISD' edges, when one edge is > 36 nm : 25nm") -lisd.space(27.nm, euclidian).polygons.not_interacting(lisd.edges.with_length(36.nm..100.mm)).output("LISD.S.3-4", "LISD.S.3-4 : Min. spacing between two LISD' edges, when both edge are =< 36 nm : 27nm") -lisd.with_area(0 .. 0.000648).output("LISD.A.1", "LISD.A.1 : Min. area of LISD : 648nm2") -lisd.and(sramdrc).with_area(0).output("SRAM.LISD.AUX.1", "SRAM.LISD.AUX.1 : LISD, which are either completely outside the SRAMDRC or which do not intersect the SRAMDRC, may not touch the SRAMDRC") - -### LIG -lig.width(16.nm).output("LIG.W.1", "LIG.W.1 : Min. width of LIG : 16nm") -lig.space(18.nm).polygons.not_interacting(lig.edges.with_length(0..36.nm)).output("LIG.S.1", "LIG.S.1 : Min. spacing between two LIG' edges, when both edges are > 36 nm : 18nm") -lig.space(25.nm).polygons.not_interacting(lig.edges.with_length(36.nm..100.mm)).output("LISD.S.2", "LIG.S.2 : Min. spacing between two LIG' edges, when one edge is > 36 nm : 25nm") -lig.space(27.nm).polygons.not_interacting(lig.edges.with_length(0..24.nm)).not_interacting(lig.edges.with_length(36.nm..100.mm)).output("LIG.S.3", "LIG.S.3 : Min. spacing between two LIG' edges, when both edges are >=24nm and =< 36 nm : 27nm") -lig.space(31.nm, euclidian).polygons.not_interacting(lisd.edges.with_length(24.nm..100.mm)).output("LIG.S.4-5", "LISD.S.4-5 : Min. spacing between two LIG' edges, when both edge are =< 24 nm : 31nm") -lig.separation(lisd, 14.nm, projection).output("LIG.LISD.S.6" "LIG.LISD.S.6 : Min. spacing between LIG and LISD not on the same net : 14nm") -lig.separation(lisd, 15.nm, euclidian).output("LIG.LISD.S.7" "LIG.LISD.S.7 : Min. corner-to-corner spacing between LISD and LIG not on the same net : 15nm") -lig.separation(sdt, 14.nm, projection).output("LIG.SDT.S.8" "LIG.SDT.S.8 : Min. spacing between LIG and SDT not on the same net : 14nm") -lig.separation(gatenogcut, 14.nm, projection).with_angle(0).output("LIG.LISD.S.9A" "LIG.LISD.S.9A : Min. vertical spacing between LIG and GATE (not cut using GCUT) : 14nm") -lig.separation(gatenogcut, 17.nm, projection).with_angle(90).output("LIG.LISD.S.9B" "LIG.LISD.S.9B : Min. horizontal spacing between LIG and GATE (not cut using GCUT) : 17nm") -lig.separation(gatenogcut & active, 5.nm, euclidian).output("LIG.GATE.S.10" "LIG.GATE.S.10 : Min. spacing between LIG and GATE (not cut using GCUT) forming a channel : 5nm") -lig.separation(gcut, 5.nm, euclidian).with_angle(0).output("LIG.GCUT.S.11" "LIG.GCUT.S.11 : Min. vertical spacing between LIG and GCUT : 5nm") -lig.with_area(0 .. 0.000324).output("LIG.A.1", "LIG.A.1 : Min. area of LIG : 324nm2") -(lig + lisd).with_area(0 .. 0.000128).output("LIG.LISD.A.2", "LIG.LISD.A.2 : Min. area of overlap between LIG and LISD : 128nm2") -(lignosram + gatenogcut).with_area(0 .. 0.000320).output("LIG.GATE.A.3", "LIG.GATE.A.3 : Min. area of overlap between LIG and and GATE (not cut using GCUT) : 320nm2") -(ligsram + gatenogcut).with_area(0 .. 0.000240).output("LIG.GATE.A.4", "LIG.GATE.A.4 : Min. area of overlap between LIG in SRAM and and GATE (not cut using GCUT) : 240nm2") -lignosram.edges.with_angle(90).and(gate).output("LIG.GATE.AUX.1", "LIG.GATE.AUX.1 : LIG vertical edge may not lie inside, or coincide with, the GATE layer") -ligsram.with_area(0).output("SRAM.LIG.AUX.2", "SRAM.LIG.AUX.2 : LIG, which are either completely outside the SRAMDRC or which do not intersect the SRAMDRC, may not touch the SRAMDRC") -lignosram.enclosing(gatenogcut, 1.nm, projection).polygons.without_area(0).output("LIG.GATE.EX.1", "LIG.GATE.EX.1 : Min. extension of LIG past GATE (not cut using GCUT) : 1nm") -(lig + lisd).width(8.nm).output("LIG.LISD.OV.1", "LIG.LISD.OV.1 : Min. overlap between LIG and LISD connected together : 8nm") -(ligsram + gatenogcut).width(15.nm).output("SRAM.LIG.GATE.OV.2", "SRAM.LIG.GATE.OV.2 : Min. overlap between LIG in SRAM and GATE (not cut using GCUT) : 15nm") - - -### V0 -# rule V0.W.1 not checked for the instance along the length of M1 -v0.width(18.nm).output("V0.W.1", "V0.W.1 : Min. width of V0 : 18nm") -v0.space(18.nm, projection).output("V0.S.1", "V0.S.1 : Min. spacing between V0 instances [on the same M1 track or on parallel M1 tracks, if they are fully or partially aligned with each other : 18nm") -v0spaceortho = v0.space(27.nm, projection).polygons -v0.space(27.nm, euclidian).polygons.not_interacting(v0spaceortho).output("V0.S.1", "V0.S.1 : Min. spacing between V0 instances on parallel M1 tracks, if they are not aligned with each other : 27nm") -m1ovlp5v0 = (m1 & v0.sized(5.nm) - v0).sized(-2.5.nm+1.dbu).sized(2.5.nm-1.dbu) -v0.edges.and(m1ovlp5v0).space(23.nm, euclidian).polygons.not(v0spaceortho).output("V0.S.2", "V0.S.2 : Min. corner-to-corner spacing between two V0 instances—both with a 5 nm M1 end-cap : 23nm") -v0.edges.not_interacting(m1ovlp5v0).space(30.nm, euclidian).output("V0.S.3", "V0.S.3 : Min. corner-to-corner spacing between two V0 instances—both without a 5 nm M1 end-cap : 30nm") -v0.edges.and(m1ovlp5v0).separation(v0.edges.not_interacting(m1ovlp5v0), 27.nm, euclidian).output("V0.S.4", "V0.S.4 : Min. corner-to-corner spacing between two V0 instances—one with and another without, a 5 nm M1 end-cap : 27nm") -v0.not_interacting(m1ovlp5v0.sized(2.dbu)).output("V0.M1.EN.1", "V0.M1.EN.1 : enclosure of V0 by M1 on one side : 5nm") -v0spaceortho.forget -m1ovlp5v0.forget -(lisd.enclosing(v0.not_interacting(lig), 3.nm, projection, two_connected_sides_allowed).edges - lisd.enclosing(v0.not_interacting(lig), 3.nm, projection, one_side_allowed).edges).output("V0.LISD.EN.2", "V0.LISD.EN.2 : Min. enclosure of V0 (interacting with LISD, but not with LIG) by LISD on at least two opposite sides is 3 nm and such a V0 must lie completely inside LISD : 3nm") -lisd.enclosing(v0.interacting(lig), 3.nm, projection, one_side_allowed).output("V0.LISD.EN.3", "V0.LISD.EN.3 : Min. enclosure of V0 (sharing some, but not all its area with LISD) by LISD (interacting with LIG) on at least two opposite sides is 3 nm and such a V0 does not need to lie completely inside LISD : 3nm") -(v0.enclosing(lig, 1.nm, projection, two_connected_sides_allowed).edges - v0.enclosing(lig, 1.nm, projection, one_side_allowed).edges).not(lig).output("V0.LIG.EN.4", "V0.LIG.EN.4 : Min. enclosure of LIG by V0 (interacting with LIG and not inside LISD) on two opposite sides is 1 nm : 1nm") -(v0 & lig).with_area(0 .. 0.000288).output("V0.LIG.A.1", "V0.LIG.A.1 : Min. area of overlap between V0 (interacting with LIG and not inside LISD) and LIG : 288nm2") -((v0 - m1) + (v0 - lig.sized(1.nm) - lisd)).output("V0.AUX.1-2" "V0.AUX.1-2 : V0 must always interact with M1 and [LISD|LIG]") -v0.not_interacting(m1.enclosing(v0, 1.dbu, projection, two_opposite_sides_allowed).edges).output("V0.M1.AUX.3", "V0.M1.AUX.3 : V0 must exactly be the same width as M1 along the direction perpendicular to the M1 length") - - -### M1 -m1.width(18.nm).output("M1.W.1", "M1.W.1 : Min. width of M1 : 18nm") -m1.space(18.nm).polygons.not_interacting(m1.edges.with_length(0..36.nm)).output("M1.S.1", "M1.S.1 : Min. spacing between two M1' edges, when both edges are > 36 nm : 18nm") -m1.space(25.nm).polygons.not_interacting(m1.edges.with_length(36.nm..100.mm)).output(" ", "M1.S.2 : Min. spacing between two M1' edges, when one edge is > 36 nm : 25nm") -m1.space(27.nm).polygons.not_interacting(m1.edges.with_length(0..24.nm)).not_interacting(m1.edges.with_length(36.nm..100.mm)).output("M1.S.3", "M1.S.3 : Min. spacing between two M1' edges, when both edges are >=24nm and =< 36 nm : 27nm") -m1.space(31.nm).polygons.not_interacting(m1.edges.with_length(24.nm..100.mm)).output("M1.S.4-5", "M1.S.4-5 : Min. spacing between two M1' edges, when both edge are =< 24 nm : 31nm") -m1long = m1.space(20.nm, projection).polygons.interacting(m1.edges.with_length(36.nm..100.mm)) -m1.space(20.nm, euclidian).polygons.not_interacting(m1long).output("M1.S.6", "M1.S.6 : Min. corner-to-corner spacing between two M1 : 20nm") -m1long.forget -m1.with_area(0 .. 0.000504).output("M1.A.1", "M1.A.1 : Min. area of M1 : 504nm2") - -### M2 -m2.width(18.nm).output("M2.W.1", "M2.W.1 : Min. width of M2 : 18nm") -m2.space(18.nm).polygons.not_interacting(m2.edges.with_length(0..36.nm)).output("M2.S.1", "M2.S.1 : Min. spacing between two M2' edges, when both edges are > 36 nm : 18nm") -m2.space(25.nm).polygons.not_interacting(m2.edges.with_length(36.nm..100.mm)).output("M2.S.2", "M2.S.2 : Min. spacing between two M2' edges, when one edge is > 36 nm : 25nm") -m2.space(27.nm).polygons.not_interacting(m2.edges.with_length(0..24.nm)).not_interacting(m2.edges.with_length(36.nm..100.mm)).output("M2.S.3", "M2.S.3 : Min. spacing between two M2' edges, when both edges are >=24nm and =< 36 nm : 27nm") -m2.space(31.nm).polygons.not_interacting(m2.edges.with_length(24.nm..100.mm)).output("M2.S.4-5", "M2.S.4-5 : Min. spacing between two M2' edges, when both edge are =< 24 nm : 31nm") -m2long = m2.space(20.nm, projection).polygons.interacting(m2.edges.with_length(36.nm..100.mm)) -m2.space(20.nm, euclidian).polygons.not_interacting(m2long).output("M2.S.6", "M2.S.6 : Min. corner-to-corner spacing between two M2 : 20nm") -m2long.forget -m2.with_area(0 .. 0.000504).output("M2.A.1", "M2.A.1 : Min. area of M2 : 504nm2") - -### M3 -m3.width(18.nm).output("M3.W.1", "M3.W.1 : Min. width of M3 : 18nm") -m3.space(18.nm).polygons.not_interacting(m3.edges.with_length(0..36.nm)).output("M3.S.1", "M3.S.1 : Min. spacing between two M3' edges, when both edges are > 36 nm : 18nm") -m3.space(25.nm).polygons.not_interacting(m3.edges.with_length(36.nm..100.mm)).output("M3.S.2", "M3.S.2 : Min. spacing between two M3' edges, when one edge is > 36 nm : 25nm") -m3.space(27.nm).polygons.not_interacting(m3.edges.with_length(0..24.nm)).not_interacting(m3.edges.with_length(36.nm..100.mm)).output("M3.S.3", "M3.S.3 : Min. spacing between two M3' edges, when both edges are >=24nm and =< 36 nm : 27nm") -m3.space(31.nm).polygons.not_interacting(m3.edges.with_length(24.nm..100.mm)).output("M3.S.4-5", "M3.S.4-5 : Min. spacing between two M3' edges, when both edge are =< 24 nm : 31nm") -m3long = m3.space(20.nm, projection).polygons.interacting(m3.edges.with_length(36.nm..100.mm)) -m3.space(20.nm, euclidian).polygons.not_interacting(m3long).output("M3.S.6", "M3.S.6 : Min. corner-to-corner spacing between two M3 : 20nm") -m3long.forget -m3.with_area(0 .. 0.000504).output("M3.A.1", "M3.A.1 : Min. area of M3 : 504nm2") - - -### V1 -# rule V1.W.1 not checked for the instance along the length of M2 -v1.width(18.nm).output("V1.W.1", "V1.W.1 : Min. width of V1 : 18nm") -v1.space(18.nm, projection).output("V1.S.1", "V1.S.1 : Min. spacing between V1 instances [on the same M2 track or on parallel M2 tracks, if they are fully or partially aligned with each other : 18nm") -v1spaceortho = v1.space(27.nm, projection).polygons -v1.space(27.nm, projection).polygons.not_interacting(v1spaceortho).output("V1.S.1", "V1.S.1 : Min. spacing between V1 instances on parallel M2 tracks, if they are not aligned with each other : 27nm") -m2ovlp5v1 = (m2 & v1.sized(5.nm + 2.dbu) - v1).sized(-2.5.nm).sized(2.5.nm) -v1.edges.and(m2ovlp5v1).space(23.nm, euclidian).polygons.not(v1spaceortho).output("V1.S.2", "V1.S.2 : Min. corner-to-corner spacing between two V1 instances—both with a 5 nm M2 end-cap : 23nm") -v1.edges.not_interacting(m2ovlp5v1).space(30.nm, euclidian).output("V1.S.3", "V1.S.3 : Min. corner-to-corner spacing between two V1 instances—both without a 5 nm M2 end-cap : 30nm") -v1.edges.and(m2ovlp5v1).separation(v1.edges.not_interacting(m2ovlp5v1), 27.nm, euclidian).output("V1.S.4", "V1.S.4 : Min. corner-to-corner spacing between two V1 instances—one with and another without, a 5 nm M2 end-cap : 27nm") -v1spaceortho.forget -m1ovlp5v1 = (m1 & v1.sized(5.nm) - v1).sized(-2.5.nm+1.dbu).sized(2.5.nm-1.dbu) -v1.interacting(m1.enclosing(v1, 2.nm, projection, two_connected_sides_allowed).edges - m1.enclosing(v1, 2.nm, projection, one_side_allowed).edges).output("V1.M1.EN.1", "V1.M1.EN.1 : Min. enclosure of V1 by M1 on at least two opposite sides : 5nm & 2nm") -v1.interacting(m1.enclosing(v1, 5.nm, projection, two_connected_sides_allowed).edges - m1.enclosing(v1, 5.nm, projection, one_side_allowed).edges).not_interacting(m1ovlp5v1.sized(1.dbu)).output("V1.M1.EN.1", "V1.M1.EN.1 : Min. enclosure of V1 by M1 on at least two opposite sides : 5nm & 2nm") -v1.not_interacting(m2ovlp5v1.sized(2.dbu)).output("V1.M2.EN.2", "V1.M2.EN.2 : enclosure of V1 by M2 on one side : 5nm") -m1ovlp5v1.forget -m2ovlp5v1.forget -((v1 - m2) + (v1 - m1)).output("V1.AUX.1" "V1.AUX.1 : V1 must be inside M1 and M2") -v1.not_interacting(m2.enclosing(v1, 1.dbu, projection, two_opposite_sides_allowed).edges).output("V1.M2.AUX.2", "V1.M2.AUX.2 : V1 must exactly be the same width as M2 along the direction perpendicular to the M2 length") - -### V2 -# rule V2.W.1 not checked for the instance along the length of M3 -v2.width(18.nm).output("V2.W.1", "V2.W.1 : Min. width of V2 : 18nm") -v2.space(18.nm, projection).output("V2.S.1", "V2.S.1 : Min. spacing between V2 instances [on the same M3 track or on parallel M3 tracks, if they are fully or partially aligned with each other : 18nm") -v2spaceortho = v2.space(27.nm, projection).polygons -v2.space(27.nm, projection).polygons.not_interacting(v2spaceortho).output("V2.S.1", "V2.S.1 : Min. spacing between V2 instances on parallel M3 tracks, if they are not aligned with each other : 27nm") -m3ovlp5v2 = (m3 & v2.sized(5.nm) - v2).sized(-2.5.nm+1.dbu).sized(2.5.nm) -v2.edges.and(m3ovlp5v2).space(23.nm, euclidian).polygons.not(v2spaceortho).output("V2.S.2", "V2.S.2 : Min. corner-to-corner spacing between two V2 instances—both with a 5 nm M3 end-cap : 23nm") -v2.edges.not_interacting(m3ovlp5v2).space(30.nm, euclidian).output("V2.S.3", "V2.S.3 : Min. corner-to-corner spacing between two V2 instances—both without a 5 nm M3 end-cap : 30nm") -v2.edges.and(m3ovlp5v2).separation(v2.edges.not_interacting(m3ovlp5v2), 27.nm, euclidian).output("V2.S.4", "V2.S.4 : Min. corner-to-corner spacing between two V2 instances—one with and another without, a 5 nm M3 end-cap : 27nm") -v2.interacting(m2.enclosing(v2, 5.nm, projection, two_connected_sides_allowed).edges - m2.enclosing(v2, 5.nm, projection, one_side_allowed).edges).output("V2.M2.EN.1", "V2.M2.EN.1 : Min. enclosure of V2 by M2 on at least two opposite sides : 5nm") -v2.not_interacting(m3ovlp5v2.sized(2.dbu)).output("V2.M3.EN.2", "V2.M3.EN.2 : enclosure of V2 by M3 on one side : 5nm") -m3ovlp5v2.forget -((v2 - m3) + (v2 - m2)).output("V2.AUX.1" "V2.AUX.1 : V2 must be inside M2 and M3") -v2.not_interacting(m3.enclosing(v2, 1.dbu, projection, two_opposite_sides_allowed).edges).output("V2.M3.AUX.2", "V2.M3.AUX.2 : V2 must exactly be the same width as M3 along the direction perpendicular to the M3 length") - -### V3 -# rule V3.W.1 not checked for the instance along the length of M4 -v3.width(18.nm).output("V3.W.1", "V3.W.1 : Min. width of V3 : 18nm") -v3.space(18.nm, projection).output("V3.S.1", "V3.S.1 : Min. spacing between V3 instances [on the same M4 track or on parallel M4 tracks, if they are fully or partially aligned with each other : 18nm") -v3spaceortho = v3.space(27.nm, projection).polygons -v3.space(27.nm, projection).polygons.not_interacting(v3spaceortho).output("V3.S.1", "V3.S.1 : Min. spacing between V3 instances on parallel M4 tracks, if they are not aligned with each other : 27nm") -m4ovlp5v3 = (m4 & v3.sized(5.nm) - v3).sized(-2.5.nm+1.dbu).sized(2.5.nm) -v3.edges.and(m4ovlp5v3).space(23.nm, euclidian).polygons.not(v3spaceortho).output("V3.S.2", "V3.S.2 : Min. corner-to-corner spacing between two V3 instances—both with a 5 nm M4 end-cap : 23nm") -v3.edges.not_interacting(m4ovlp5v3).space(30.nm, euclidian).output("V3.S.3", "V3.S.3 : Min. corner-to-corner spacing between two V3 instances—both without a 5 nm M4 end-cap : 30nm") -v3.edges.and(m4ovlp5v3).separation(v3.edges.not_interacting(m4ovlp5v3), 27.nm, euclidian).output("V3.S.4", "V3.S.4 : Min. corner-to-corner spacing between two V3 instances—one with and another without, a 5 nm M4 end-cap : 27nm") -m4ovlp5v3.forget -v3.interacting(m3.enclosing(v3, 5.nm, projection, two_connected_sides_allowed).edges - m3.enclosing(v3, 5.nm, projection, one_side_allowed).edges).output("V3.M3.EN.1", "V3.M3.EN.1 : Min. enclosure of V3 by M3 on at least two opposite sides : 5nm") -v3.interacting(m4.enclosing(v3, 11.nm, projection, two_connected_sides_allowed).edges - m4.enclosing(v3, 11.nm, projection, one_side_allowed).edges).output("V3.M4.EN.2", "V3.M4.EN.2 : Min. enclosure of V3 by M4 on at least two opposite sides : 11nm") -((v3 - m4) + (v3 - m3)).output("V3.AUX.1" "V3.AUX.1 : V3 must be inside M3 and M4") -v3.not_interacting(m4.enclosing(v3, 1.dbu, projection, two_opposite_sides_allowed).edges.with_angle(0)).output("V3.M4.AUX.2", "V3.M4.AUX.2 : V3 must exactly be the same width as M4 along the direction perpendicular to the M4 length") - - -### M4 -m4.width(24.nm, projection).with_angle(0).output("M4.W.1", "M4.W.1 : Min. vertical width of M4 : 24nm") -m4.sized(0, -240.nm).sized(0, 240.nm).output("M4.W.2", "M4.W.2 : Max. vertical width of M4 : 480nm") -m4vedg = m4.width(481.nm, projection).edges.with_angle(90) -(m4vedg.with_length(48.nm) + m4vedg.with_length(96.nm) + m4vedg.with_length(144.nm) + m4vedg.with_length(192.nm) + m4vedg.with_length(240.nm) + m4vedg.with_length(288.nm) + m4vedg.with_length(336.nm) + m4vedg.with_length(384.nm) + m4vedg.with_length(432.nm) + m4vedg.with_length(480.nm)).output("M4.W.3", "M4.W.3 : M4 vertical width may not be an even integer multiple of its minimum width") -# rule M4.W.4 not coded -m4.width(44.nm, projection).edges.with_angle(90).output("M4.W.5", "M4.W.5 : Min. horizontal width of M4 : 44nm") -m4.space(24.nm, projection).edges.with_angle(0).output("M4.S.1", "M4.S.1 : Min. vertical spacing between M4 : 24nm") -m4s2 = m4.space(40.nm, projection).edges.with_angle(90) -m4s2.output("M4.S.2", "M4.S.2 : Min. horizontal spacing between M4 : 40nm") -m4.sized(0,48.nm).space(40.nm, projection).edges.with_angle(90).not(m4s2).and(m4).output("M4.S.3", "M4.S.3 : Min. horizontal tip-to-tip spacing between two M4 on adjacent tracks : 40nm") -m4.sized(0,48.nm).notch(40.nm, projection).edges.with_angle(90).not(m4s2).and(m4).output("M4.S.4", "M4.S.4 : Min. horizontal tip-to-tip spacing between two M4 on adjacent tracks : 40nm") -m4.space(25.nm, projection).edges.with_angle(0).with_length(0..44.nm).output("M4.S.5", "M4.S.5 : Min. parallel run length of two M4 on adjacent tracks : 44nm") -# rule M4.AUX.1 coded in ONGRID section -# rule M4.AUX.2 not coded -m4.corners(0..90).sized(1.nm).output("M4.AUX.3", "M4.AUX.3 : M4 may not bend") -# rule M4.AUX.4 not coded because the routing track is not defined - -### M5 -m5.width(24.nm, projection).with_angle(90).output("M5.W.1", "M5.W.1 : Min. horizontal width of M5 : 24nm") -m5.sized(-240.nm, 0).sized(240.nm, 0).output("M5.W.2", "M5.W.2 : Max. horizontal width of M5 : 480nm") -m5vedg = m5.width(481.nm, projection).edges.with_angle(0) -(m5vedg.with_length(48.nm) + m5vedg.with_length(96.nm) + m5vedg.with_length(144.nm) + m5vedg.with_length(192.nm) + m5vedg.with_length(240.nm) + m5vedg.with_length(288.nm) + m5vedg.with_length(336.nm) + m5vedg.with_length(384.nm) + m5vedg.with_length(432.nm) + m5vedg.with_length(480.nm)).output("M5.W.3", "M5.W.3 : M5 horizontal width may not be an even integer multiple of its minimum width") -# rule M5.W.4 not coded -m5.width(44.nm, projection).edges.with_angle(0).output("M5.W.5", "M5.W.5 : Min. vertical width of M5 : 44nm") -m5.space(24.nm, projection).edges.with_angle(90).output("M5.S.1", "M5.S.1 : Min. horizontal spacing between M5 : 24nm") -m5s2 = m5.space(40.nm, projection).edges.with_angle(0) -m5s2.output("M5.S.2", "M5.S.2 : Min. vertical spacing between M5 : 40nm") -m5.sized(0,48.nm).space(40.nm, projection).edges.with_angle(0).not(m5s2).and(m5).output("M5.S.3", "M5.S.3 : Min. vertical tip-to-tip spacing between two M5 on adjacent tracks : 40nm") -m5.sized(0,48.nm).notch(40.nm, projection).edges.with_angle(0).not(m5s2).and(m5).output("M5.S.4", "M5.S.4 : Min. vertical tip-to-tip spacing between two M5 on adjacent tracks : 40nm") -m5.space(25.nm, projection).edges.with_angle(90).with_length(0..44.nm).output("M5.S.5", "M5.S.5 : Min. parallel run length of two M5 on adjacent tracks : 44nm") -# rule M5.AUX.1 coded in ONGRID section -# rule M5.AUX.2 not coded -m5.corners(0..90).sized(1.nm).output("M5.AUX.3", "M5.AUX.3 : M5 may not bend") -# rule M5.AUX.4 not coded because the routing track is not defined - - -### V4 -# rule V4.W.1 not checked for the instance along the length of M5 -v4.width(24.nm).output("V4.W.1", "V4.W.1 : Min. width of V4 : 24nm") -v4.space(33.nm, euclidian).output("V4.S.1-2-3", "V4.S.1-2-3 : Min. spacing between V4 : 33nm") -v4.interacting(m4.enclosing(v4, 11.nm, projection, two_connected_sides_allowed).edges - m4.enclosing(v4, 11.nm, projection, one_side_allowed).edges).output("V4.M4.EN.1", "V4.M4.EN.1 : Min. enclosure of V4 by M4 on at least two opposite sides : 11nm") -((v4 - m4) + (v4 - m5)).output("V4.AUX.1" "V4.AUX.1 : V4 must be inside M4 and M5") -v4.not_interacting(m5.enclosing(v4, 1.dbu, projection, two_opposite_sides_allowed).edges.with_angle(90)).output("V4.M5.AUX.2", "V4.M5.AUX.2 : V4 must exactly be the same width as M5 along the direction perpendicular to the M5 length") - -### V5 -# rule V5.W.1 not checked for the instance along the length of M6 -v5.width(24.nm).output("V5.W.1", "V5.W.1 : Min. width of V5 : 24nm") -v5.space(33.nm, euclidian).output("V5.S.1-2-3", "V5.S.1-2-3 : Min. spacing between V5 : 33nm") -v5.interacting(m5.enclosing(v5, 11.nm, projection, two_connected_sides_allowed).edges - m5.enclosing(v5, 11.nm, projection, one_side_allowed).edges).output("V5.M5.EN.1", "V5.M5.EN.1 : Min. enclosure of V5 by M5 on at least two opposite sides : 11nm") -((v5 - m5) + (v5 - m6)).output("V5.AUX.1" "V5.AUX.1 : V5 must be inside M5 and M6") -v5.not_interacting(m6.enclosing(v5, 1.dbu, projection, two_opposite_sides_allowed).edges.with_angle(90)).output("V5.M6.AUX.2", "V5.M6.AUX.2 : V5 must exactly be the same width as M6 along the direction perpendicular to the M6 length") - - -### M6 -m6.width(32.nm, projection).with_angle(0).output("M6.W.1", "M6.W.1 : Min. vertical width of M6 : 32nm") -m6.sized(0, -320.nm).sized(0, 320.nm).output("M6.W.2", "M6.W.2 : Max. vertical width of M6 : 640nm") -m6vedg = m6.width(641.nm, projection).edges.with_angle(90) -(m6vedg.with_length(64.nm) + m6vedg.with_length(128.nm) + m6vedg.with_length(192.nm) + m6vedg.with_length(256.nm) + m6vedg.with_length(320.nm) + m6vedg.with_length(384.nm) + m6vedg.with_length(448.nm) + m6vedg.with_length(512.nm) + m6vedg.with_length(576.nm) + m6vedg.with_length(640.nm)).output("M6.W.3", "M6.W.3 : M6 vertical width may not be an even integer multiple of its minimum width") -# rule M6.W.4 not coded -m6.width(44.nm, projection).edges.with_angle(90).output("M6.W.5", "M6.W.5 : Min. horizontal width of M6 : 44nm") -m6.space(32.nm, projection).edges.with_angle(0).output("M6.S.1", "M6.S.1 : Min. vertical spacing between M6 : 32nm") -m6s2 = m6.space(40.nm, projection).edges.with_angle(90) -m6s2.output("M6.S.2", "M6.S.2 : Min. horizontal spacing between M6 : 40nm") -m6.sized(0,48.nm).space(40.nm, projection).edges.with_angle(90).not(m6s2).and(m6).output("M6.S.3", "M6.S.3 : Min. horizontal tip-to-tip spacing between two M6 on adjacent tracks : 40nm") -m6.sized(0,48.nm).notch(40.nm, projection).edges.with_angle(90).not(m6s2).and(m6).output("M6.S.4", "M6.S.4 : Min. horizontal tip-to-tip spacing between two M6 on adjacent tracks : 40nm") -m6.space(25.nm, projection).edges.with_angle(0).with_length(0..44.nm).output("M6.S.5", "M6.S.5 : Min. parallel run length of two M6 on adjacent tracks : 44nm") -# rule M6.AUX.1 coded in ONGRID section -# rule M6.AUX.2 not coded -m6.corners(0..90).sized(1.nm).output("M6.AUX.3", "M6.AUX.3 : M6 may not bend") -# rule M6.AUX.4 not coded because the routing track is not defined - -### M7 -m7.width(32.nm, projection).with_angle(90).output("M7.W.1", "M7.W.1 : Min. horizontal width of M7 : 32nm") -m7.sized(-320.nm, 0).sized(320.nm, 0).output("M7.W.2", "M7.W.2 : Max. horizontal width of M7 : 640nm") -m7vedg = m7.width(641.nm, projection).edges.with_angle(0) -(m7vedg.with_length(64.nm) + m7vedg.with_length(128.nm) + m7vedg.with_length(192.nm) + m7vedg.with_length(256.nm) + m7vedg.with_length(320.nm) + m7vedg.with_length(384.nm) + m7vedg.with_length(448.nm) + m7vedg.with_length(512.nm) + m7vedg.with_length(576.nm) + m7vedg.with_length(640.nm)).output("M7.W.3", "M7.W.3 : M7 horizontal width may not be an even integer multiple of its minimum width") -# rule M7.W.4 not coded -m7.width(44.nm, projection).edges.with_angle(0).output("M7.W.5", "M7.W.5 : Min. vertical width of M7 : 44nm") -m7.space(32.nm, projection).edges.with_angle(90).output("M7.S.1", "M7.S.1 : Min. horizontal spacing between M7 : 32nm") -m7s2 = m7.space(40.nm, projection).edges.with_angle(0) -m7s2.output("M7.S.2", "M7.S.2 : Min. vertical spacing between M7 : 40nm") -m7.sized(0,48.nm).space(40.nm, projection).edges.with_angle(0).not(m7s2).and(m7).output("M7.S.3", "M7.S.3 : Min. vertical tip-to-tip spacing between two M7 on adjacent tracks : 40nm") -m7.sized(0,48.nm).notch(40.nm, projection).edges.with_angle(0).not(m7s2).and(m7).output("M7.S.4", "M7.S.4 : Min. vertical tip-to-tip spacing between two M7 on adjacent tracks : 40nm") -m7.space(25.nm, projection).edges.with_angle(90).with_length(0..44.nm).output("M7.S.5", "M7.S.5 : Min. parallel run length of two M7 on adjacent tracks : 44nm") -# rule M7.AUX.1 coded in ONGRID section -# rule M7.AUX.2 not coded -m7.corners(0..90).sized(1.nm).output("M7.AUX.3", "M7.AUX.3 : M7 may not bend") -# rule M7.AUX.4 not coded because the routing track is not defined - - -### V6 -# rule V6.W.1 not checked for the instance along the length of M7 -v6.width(32.nm).output("V6.W.1", "V6.W.1 : Min. width of V6 : 32nm") -v6.space(45.nm, euclidian).output("V6.S.1-2-3", "V6.S.1-2-3 : Min. spacing between V6 : 45nm") -v6.interacting(m6.enclosing(v6, 11.nm, projection, two_connected_sides_allowed).edges - m6.enclosing(v6, 11.nm, projection, one_side_allowed).edges).output("V6.M6.EN.1", "V6.M6.EN.1 : Min. enclosure of V6 by M6 on at least two opposite sides : 11nm") -((v6 - m6) + (v6 - m7)).output("V6.AUX.1" "V6.AUX.1 : V6 must be inside M6 and M7") -v6.not_interacting(m7.enclosing(v6, 1.dbu, projection, two_opposite_sides_allowed).edges.with_angle(90)).output("V6.M7.AUX.2", "V6.M7.AUX.2 : V6 must exactly be the same width as M7 along the direction perpendicular to the M7 length") - -### V7 -# rule V7.W.1 not checked for the instance along the length of M8 -v7.width(32.nm).output("V7.W.1", "V7.W.1 : Min. width of V7 : 32nm") -v7.space(45.nm, euclidian).output("V7.S.1-2-3", "V7.S.1-2-3 : Min. spacing between V7 : 45nm") -v7.interacting(m7.enclosing(v7, 11.nm, projection, two_connected_sides_allowed).edges - m7.enclosing(v7, 11.nm, projection, one_side_allowed).edges).output("V7.M7.EN.1", "V7.M7.EN.1 : Min. enclosure of V7 by M7 on at least two opposite sides : 11nm") -((v7 - m7) + (v7 - m8)).output("V7.AUX.1" "V7.AUX.1 : V7 must be inside M7 and M8") -v7.not_interacting(m8.enclosing(v7, 1.dbu, projection, two_opposite_sides_allowed).edges.with_angle(90)).output("V7.M8.AUX.2", "V7.M8.AUX.2 : V7 must exactly be the same width as M8 along the direction perpendicular to the M8 length") - - -### M8 -m8.width(40.nm).output("M8.W.1", "M8.W.1 : Min. width of M8 : 40nm") -m8.width(60.nm, projection).edges.with_length(0..400.nm).output("M8.W.2", "M8.W.2 : Min. width of M8, when its length >= 400 nm and < 1200 nm : 60nm") -m8.width(80.nm, projection).edges.with_length(400.nm..1200.nm).output("M8.W.3", "M8.W.3 : Min. width of M8, when its length >= 1200 nm and < 1800 nm : 80nm") -m8.width(120.nm, projection).edges.with_length(1200.nm..1800.nm).output("M8.W.4", "M8.W.3 : Min. width of M8, when its length >=1800 nm : 120nm") -m8.sized(-1.um).sized(-1.um).output("M8.W.5", "M8.W.5 : Max. width of M8 : 2000nm") -m8.space(40.nm, projection).edges.with_length(80.nm..100.mm).output("M8.S.1", "M8.S.1 : Min. spacing between two M8' edges, when both edges are >= 80 nm : 40nm") -m8.space(43.nm, projection).polygons.not_interacting(m8.edges.with_length(80.nm..100.mm)).output("M8.S.2", "M8.S.2 : Min. spacing between two M8' edges, when one of the edges is < 80 nm and the other is >= 80 nm : 43nm") -m8.space(46.nm, projection).edges.with_length(0..80.nm).output("M8.S.3", "M8.S.3 : Min. spacing between two M8' edges, when both edges are < 80 nm : 46nm") -m8.space(60.nm, projection).polygons.interacting(m8.edges.with_length(60.nm..80.nm)).output("M8.S.4", "M8.S.4 : Min. spacing between two M8' edges, when one of the edges is >= 60 nm and < 80 nm : 60nm") -m8.space(80.nm, projection).polygons.interacting(m8.edges.with_length(80.nm..120.nm)).output("M8.S.5", "M8.S.5 : Min. spacing between two M8' edges, when one of the edges is >= 80 nm and < 120 nm : 80nm") -m8.space(120.nm, projection).polygons.interacting(m8.edges.with_length(120.nm..500.nm)).output("M8.S.6", "M8.S.6 : Min. spacing between two M8' edges, when one of the edges is >= 120 nm and < 500 nm : 120nm") -m8.space(500.nm, projection).polygons.interacting(m8.edges.with_length(500.nm..1000.nm)).output("M8.S.7", "M8.S.7 : Min. spacing between two M8' edges, when one of the edges is >= 500 nm and < 1000 nm : 500nm") -m8.space(1000.nm, projection).polygons.interacting(m8.edges.with_length(1000.nm..100.mm)).output("M8.S.8", "M8.S.8 : Min. spacing between two M8' edges, when one of the edges is >= 1000 nm : 1000nm") -m8.with_area(0 .. 0.007520).output("M8.A.1", "M8.A.1 : Min. area of M8 : 7520nm2") -m8.edges.without_length(40.nm..100.mm).output("M8.L.1", "M8.L.1 : Minimum feature length of M8 : 40nm") - -### M9 -m9.width(40.nm).output("M9.W.1", "M9.W.1 : Min. width of M9 : 40nm") -m9.width(60.nm, projection).edges.with_length(0..400.nm).output("M9.W.2", "M9.W.2 : Min. width of M9, when its length >= 400 nm and < 1200 nm : 60nm") -m9.width(80.nm, projection).edges.with_length(400.nm..1200.nm).output("M9.W.3", "M9.W.3 : Min. width of M9, when its length >= 1200 nm and < 1800 nm : 80nm") -m9.width(120.nm, projection).edges.with_length(1200.nm..1800.nm).output("M9.W.4", "M9.W.3 : Min. width of M9, when its length >=1800 nm : 120nm") -m9.sized(-1.um).sized(-1.um).output("M9.W.5", "M9.W.5 : Max. width of M9 : 2000nm") -m9.space(40.nm, projection).edges.with_length(80.nm..100.mm).output("M9.S.1", "M9.S.1 : Min. spacing between two M9' edges, when both edges are >= 80 nm : 40nm") -m9.space(43.nm, projection).polygons.not_interacting(m9.edges.with_length(80.nm..100.mm)).output("M9.S.2", "M9.S.2 : Min. spacing between two M9' edges, when one of the edges is < 80 nm and the other is >= 80 nm : 43nm") -m9.space(46.nm, projection).edges.with_length(0..80.nm).output("M9.S.3", "M9.S.3 : Min. spacing between two M9' edges, when both edges are < 80 nm : 46nm") -m9.space(60.nm, projection).polygons.interacting(m9.edges.with_length(60.nm..80.nm)).output("M9.S.4", "M9.S.4 : Min. spacing between two M9' edges, when one of the edges is >= 60 nm and < 80 nm : 60nm") -m9.space(80.nm, projection).polygons.interacting(m9.edges.with_length(80.nm..120.nm)).output("M9.S.5", "M9.S.5 : Min. spacing between two M9' edges, when one of the edges is >= 80 nm and < 120 nm : 80nm") -m9.space(120.nm, projection).polygons.interacting(m9.edges.with_length(120.nm..500.nm)).output("M9.S.6", "M9.S.6 : Min. spacing between two M9' edges, when one of the edges is >= 120 nm and < 500 nm : 120nm") -m9.space(500.nm, projection).polygons.interacting(m9.edges.with_length(500.nm..1000.nm)).output("M9.S.7", "M9.S.7 : Min. spacing between two M9' edges, when one of the edges is >= 500 nm and < 1000 nm : 500nm") -m9.space(1000.nm, projection).polygons.interacting(m9.edges.with_length(1000.nm..100.mm)).output("M9.S.8", "M9.S.8 : Min. spacing between two M9' edges, when one of the edges is >= 1000 nm : 1000nm") -m9.with_area(0 .. 0.007520).output("M9.A.1", "M9.A.1 : Min. area of M9 : 7520nm2") -m9.edges.without_length(40.nm..100.mm).output("M9.L.1", "M9.L.1 : Minimum feature length of M9 : 40nm") - - -### V8 -((v8.edges.without_length(40.nm) & v8.edges.without_length(120.nm)) + v8.with_area(0.0144).edges).output("V8.W.1", "V8.W.1 : Exact width of a V8 instance") -v8.space(57.nm).output("V8.S.1-2", "V8.S.1-2 : Min. spacing between V8 : 57.nm") -v8.interacting(m8.enclosing(v8, 20.nm, projection, two_connected_sides_allowed).edges - m8.enclosing(v8, 20.nm, projection, one_side_allowed).edges).output("V8.M8.EN.1", "V8.M8.EN.1 : Min. enclosure of V8 by M8 on at least two opposite sides : 20nm") -v8.interacting(m9.enclosing(v8, 20.nm, projection, two_connected_sides_allowed).edges - m9.enclosing(v8, 20.nm, projection, one_side_allowed).edges).output("V8.M9.EN.2", "V8.M9.EN.2 : Min. enclosure of V8 by M9 on at least two opposite sides : 20nm") -((v8 - m8) + (v8 - m9)).output("V8.AUX.1" "V8.AUX.1 : V8 must be inside M8 and M9") - -### V9 -((v9.edges.without_length(40.nm) & v9.edges.without_length(120.nm)) + v9.with_area(0.0144).edges).output("V9.W.1", "V9.W.1 : Exact width of a V9 instance") -v9.space(57.nm).output("V9.S.1-2", "V9.S.1-2 : Min. spacing between V9 : 57.nm") -v9.interacting(m9.enclosing(v9, 20.nm, projection, two_connected_sides_allowed).edges - m9.enclosing(v9, 20.nm, projection, one_side_allowed).edges).output("V9.M9.EN.1", "V9.M9.EN.1 : Min. enclosure of V9 by M9 on at least two opposite sides : 20nm") -v9.interacting(pad.enclosing(v9, 20.nm, projection, two_connected_sides_allowed).edges - pad.enclosing(v9, 20.nm, projection, one_side_allowed).edges).output("V9.PAD.EN.2", "V9.PAD.EN.2 : Min. enclosure of V9 by PAD on at least two opposite sides : 20nm") -((v9 - m9) + (v9 - pad)).output("V9.AUX.1" "V9.AUX.1 : V9 must be inside M9 and PAD") - - -# ONGRID defined in the paper : -# "ASAP7 : A 7-nm finFET predictive process design kit" -########################################################## -if OFFGRID -info("GRID section") - -# special grids for M4.to M7 -m4.ongrid(24.nm, 0).output("M4.AUX.1", "M4.AUX.1 : M4 horizontal edges must be at a grid of : 24nm") -m5.ongrid(0, 24.nm).output("M5.AUX.1", "M5.AUX.1 : M5 vertical edges must be at a grid of : 24nm") -m6.ongrid(32.nm, 0).output("M4.AUX.1", "M4.AUX.1 : M4 horizontal edges must be at a grid of : 32nm") -m7.ongrid(0, 32.nm).output("M5.AUX.1", "M5.AUX.1 : M5 vertical edges must be at a grid of : 32nm") - -grid = 1.nm -all_drawing = [ :nwell, :fin, :gate, :gcut, :active, :sdt, :nselect, :pselect, :slvt, :lvt, :sramdrc, :sramvt, :dummy, :lig, :lisd, :v0, :m1, :v1, :m2, :v2, :m3, :v3, :m4, :v4, :m5, :v5, :m6, :v6, :m7, :v7, :m8, :v8, :m9, :v9, ] - all_drawing.each do |dwg| - # a Ruby idiom to get the value of a variable whose name is in "dwg" (as symbol) - layer = binding.local_variable_get(dwg) - layer.ongrid(grid).polygons(2.nm).output("GRID: vertexes on layer #{dwg} not on grid of #{'%.12g' % grid}") - nonortho_edges = layer.edges - layer.edges.with_angle(0) - layer.edges.with_angle(90) - nonortho_edges.output("#{dwg}.GEOMETRY.NONORTHOGONAL" , "GEOMETRY.NONORTHOGONAL on layer #{dwg}") - end -end - - diff --git a/flow/platforms/asap7/drc/asap7_drc.py b/flow/platforms/asap7/drc/asap7_drc.py new file mode 100644 index 0000000000..a56c4cfe03 --- /dev/null +++ b/flow/platforms/asap7/drc/asap7_drc.py @@ -0,0 +1,1467 @@ +########################################################################################## +# BSD 2-Clause License +# +# Copyright (c) 2021, laurentc2 +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +"""ASAP7 DRC deck, run by flow/util/klayout_drc.py on the klayout module. + +DRC for ASAP7 according to asap7_drm_201207a.pdf, found on +https://github.com/The-OpenROAD-Project/asap7/blob/master/asap7PDK_r1p7/docs/asap7_drm_201207a.pdf + +Based on work by @laurentc2 found on +https://github.com/laurentc2/ASAP7_for_KLayout, translated line by line +from KLayout's DRC language. +""" + +import klayout.db as db + +from klayout_drc import ( + Layer, + euclidian, + mm, + nm, + one_side_allowed, + projection, + two_connected_sides_allowed, + two_opposite_sides_allowed, + um, + width, +) + +DESCRIPTION = "ASAP7 DRC runset" +MODE = "deep" +THREADS = 8 + +# manufacturing grid/angle checks +OFFGRID = False + + +def with_holes(layer, lo, hi): + """Polygons with lo to hi holes, both inclusive, hi None for no limit.""" + new_data = db.Region() + for p in layer.data.each(): + if p.holes() >= (lo or 0) and (hi is None or p.holes() <= hi): + new_data.insert(p) + return Layer(layer.deck, new_data) + + +def drc(deck): + # layers definitions + nwell = deck.input(1, 0) + fin = deck.input(2, 0) + gate = deck.input(7, 0) + gcut = deck.input(10, 0) + active = deck.input(11, 0) + sdt = deck.input(88, 0) + nselect = deck.input(12, 0) + pselect = deck.input(13, 0) + slvt = deck.input(97, 0) + lvt = deck.input(98, 0) + sramdrc = deck.input(99, 0) + sramvt = deck.input(110, 0) + dummy = deck.input(8, 0) + lig = deck.input(16, 0) + lisd = deck.input(17, 0) + v0 = deck.input(18, 0) + m1 = deck.input(19, 0) + v1 = deck.input(21, 0) + m2 = deck.input(20, 0) + v2 = deck.input(25, 0) + m3 = deck.input(30, 0) + v3 = deck.input(35, 0) + m4 = deck.input(40, 0) + v4 = deck.input(45, 0) + m5 = deck.input(50, 0) + v5 = deck.input(55, 0) + m6 = deck.input(60, 0) + v6 = deck.input(65, 0) + m7 = deck.input(70, 0) + v7 = deck.input(75, 0) + m8 = deck.input(80, 0) + v8 = deck.input(85, 0) + m9 = deck.input(90, 0) + v9 = deck.input(95, 0) + pad = deck.input(96, 0) + + # DRC section + ######################## + deck.info("DRC section") + + # construction layers : + gatenogcut = gate - gcut + activesram, activenosram = active.andnot(sramdrc) + ligsram, lignosram = lig.andnot(sramdrc) + + ### WELL + nwell.width(nm(108), projection).with_angle(90).output( + "WELL.W.1", "WELL.W.1 : Min. horizontal width of WELL : 108nm" + ) + nwell.width(nm(54), projection).with_angle(0).output( + "WELL.W.2", "WELL.W.2 : Min. vertical width of WELL : 54nm" + ) + nwell.space(nm(108), projection).with_angle(0).output( + "WELL.S.1", "WELL.S.1 : Min. vertical spacing between WELL : 108nm" + ) + nwell.space(nm(54), projection).with_angle(90).output( + "WELL.S.2", "WELL.S.2 : Min. horizontal spacing between WELL : 54nm" + ) + nwell.with_area((0, 0.005832)).output( + "WELL.A.1A", "WELL.A.1A : Min. area of WELL : 5832nm2" + ) + nwell.holes().with_area((0, 0.005832)).output( + "WELL.A.1B", "WELL.A.1B : Min. enclosed area of WELL : 5832nm2" + ) + nwell.enclosing(gatenogcut, nm(7), projection).with_angle(90).output( + "WELL.GATE.EX.1", + "WELL.GATE.EX.1 : Min. horizontal extension of WELL past GATE (not cut by GCUT layer): 7nm", + ) + nwell.not_(sramdrc).enclosing(gatenogcut, nm(7), projection).with_angle(0).output( + "WELL.GATE.EX.2", + "WELL.GATE.EX.2 : Min. vertical extension of WELL past GATE (not cut by GCUT layer): 7nm", + ) + + ### FIN + fin.edges().with_angle(90).without_length(nm(7)).output( + "FIN.W.1a", "FIN.W.1 : Exact vertical width of FIN : 7nm" + ) + fin.without_bbox_height(nm(7)).output( + "FIN.W.1", "FIN.W.1 : Exact vertical width of FIN : 7nm" + ) + # fin.drc(if_all(angle == 90, length != 7.nm)).output("FIN.W.1c", "FIN.W.1 : Exact vertical width of FIN : 7nm") + fin.drc(width(projection) < nm(108)).with_angle(90).output( + "FIN.W.2", "FIN.W.2 : Min. horizontal width of WELL : 108nm" + ) + fin.space(nm(35), projection).polygons().not_(fin).without_bbox_height( + nm(20) + ).sized(0, nm(3.5)).output("FIN.S.1", "FIN.S.1 : Exact vertical FIN pitch : 27nm") + fin.corners((0, 90)).sized(nm(1)).output( + "FIN.AUX.1", "FIN.AUX.1 : FIN may not bend" + ) + + ### GATE + gate.drc(width(projection) != nm(20)).with_angle(90).output( + "GATE.W.1", "GATE.W.1 : Exact horizontal width of GATE : 20nm" + ) + gate.width(nm(40), projection).with_angle(0).output( + "GATE.W.2", "GATE.W.2 : Min. vertical width of GATE : 40nm" + ) + gate.space(nm(100), projection).polygons().not_(fin).without_bbox_width( + nm(34) + ).sized(0, nm(10)).output( + "GATE.S.1", "GATE.S.1 : Exact horizontal GATE pitch : 54nm" + ) + gate.space(nm(34), projection).with_angle(90).output( + "GATE.S.3", "GATE.S.3 : Min. horizontal spacing between GATE : 34nm" + ) + gatenogcut.not_(sramdrc).sized(nm(55), 0).not_interacting(gate).output( + "GATE.S.2", + "GATE.S.2 : Every GATE (not cut by GCUT and not interacting with the layer SRAMDRC) must have at least one other GATE within 54 nm of its surrounding along the horizontal axis : 54nm", + ) + gate.corners((0, 90)).sized(nm(1)).output( + "GATE.AUX.1", "GATE.AUX.1 : GATE may not bend" + ) + with_holes(gate.sized(1 * deck.dbu, mm(1)).and_(gate), 2, None).output( + "GATE.AUX.2", + "GATE.AUX.2 : GATE may not be discontinuous along the vertical axis", + ) + active.edges().with_angle(90).and_(gate).output( + "GATE.AUX.3", + "GATE.AUX.3 : ACTIVE layer vertical edge may not lie inside, or coincide with, the GATE layer", + ) + gate.enclosing(gate.and_(active), nm(4), projection).polygons().without_area( + 0 + ).output( + "GATE.ACTIVE.EX.1", + "GATE.ACTIVE.EX.1 : Min. vertical extension of GATE (not cut by GCUT) past ACTIVE : 4nm", + ) + active.interacting(gate).enclosing( + gate.and_(active), nm(25), projection + ).polygons().without_area(0).output( + "GATE.ACTIVE.EX.2", + "GATE.ACTIVE.EX.2 : Min. horizontal extension of ACTIVE interacting with GATE (not cut by GCUT layer) past GATE (not cut by GCUT layer) : 20nm", + ) + gatenogcut.separation(active, nm(9), projection).polygons().without_area( + 0 + ).not_interacting(gate & active).output( + "GATE.ACTIVE.S.4", + "GATE.ACTIVE.S.4 : Min. horizontal spacing between ACTIVE and GATE (not cut by GCUT and not interacting with ACTIVE) : 9nm", + ) + + ### ACTIVE + active.enclosing(fin, nm(10), projection).polygons().without_area(0).output( + "ACTIVE.FIN.EX.1", + "ACTIVE.FIN.EX.1 : Min. vertical extension of ACTIVE past FIN : 10nm", + ) + active.width(nm(27), projection).with_angle(0).output( + "ACTIVE.W.1", "ACTIVE.W.1 : Min. vertical width of ACTIVE : 27nm" + ) + active.without_bbox_height(nm(27)).and_( + active.without_bbox_height(2 * nm(27)) + ).and_(active.without_bbox_height(3 * nm(27))).and_( + active.without_bbox_height(4 * nm(27)) + ).and_( + active.without_bbox_height(5 * nm(27)) + ).and_( + active.without_bbox_height(6 * nm(27)) + ).and_( + active.without_bbox_height(7 * nm(27)) + ).and_( + active.without_bbox_height(8 * nm(27)) + ).and_( + active.without_bbox_height(9 * nm(27)) + ).and_( + active.without_bbox_height(10 * nm(27)) + ).and_( + active.without_bbox_height(11 * nm(27)) + ).and_( + active.without_bbox_height(12 * nm(27)) + ).output( + "ACTIVE.W.2", + "ACTIVE.W.2 : ACTIVE layer vertical width increment is an integer multiple of : 27nm", + ) + active.width(nm(16), projection).with_angle(90).output( + "ACTIVE.W.3", "ACTIVE.W.3 : Min. horizontal width of ACTIVE : 16nm" + ) + active.space(nm(27), projection).with_angle(0).output( + "ACTIVE.S.1", "ACTIVE.S.1 : Min. vertical spacing between ACTIVE : 27nm" + ) + active.interacting(gate).space(nm(92), projection).edges().with_angle( + 90 + ).extended_out(nm(30)).not_(lisd).output( + "ACTIVE.S.2A", + "ACTIVE.S.2A : Min. horizontal spacing between ACTIVE layers forming source/drain regions (of different transistors) : 92nm", + ) + active.space(nm(38), projection).with_angle(90).output( + "ACTIVE.S.2B", "ACTIVE.S.2B : Min. horizontal spacing between ACTIVE : 38nm" + ) + activenosram.separation(nwell, nm(27), projection).output( + "ACTIVE.WELL.S.4", "ACTIVE.WELL.S.4 : Min. spacing of ACTIVE to WELL : 27nm" + ) + activesram.separation(nwell, nm(13.5), projection).output( + "SRAM.ACTIVE.WELL.S.5", + "SRAM.ACTIVE.WELL.S.5 : Min. spacing of ACTIVE in SRAM to WELL : 13.5nm", + ) + nwell.enclosing(activenosram, nm(27), projection).polygons().without_area(0).output( + "ACTIVE.WELL.EN.1", "ACTIVE.WELL.EN.1 : Min. enclosure of ACTIVE by WELL : 27nm" + ) + nwell.enclosing(activesram, nm(13.5), projection).polygons().without_area(0).output( + "SRAM.ACTIVE.WELL.EN.2", + "SRAM.ACTIVE.WELL.EN.2 : Min. enclosure of ACTIVE in SRAM by WELL : 13.5nm", + ) + activenosram.with_area((0, 0.000864)).output( + "ACTIVE.A.1A", "ACTIVE.A.1A : Min. area of ACTIVE : 864nm2" + ) + activenosram.holes().with_area((0, 0.000864)).output( + "ACTIVE.A.1B", "ACTIVE.A.1B : Min. enclosed area of ACTIVE : 864nm2" + ) + activesram.with_area((0, 0.000432)).output( + "SRAM.ACTIVE.A.1A", "SRAM.ACTIVE.A.1A : Min. area of ACTIVE in SRAM : 432nm2" + ) + activesram.holes().with_area((0, 0.000432)).output( + "SRAM.ACTIVE.A.1B", + "SRAM.ACTIVE.A.1B : Min. enclosed area of ACTIVE in SRAM : 432nm2", + ) + active.not_(nselect + pselect).output( + "ACTIVE.AUX.1", + "ACTIVE.AUX.1 : ACTIVE must always be enclosed by NSELECT or PSELECT", + ) + active.edges().and_((nselect + pselect).edges()).output( + "ACTIVE.AUX.1_edges", + "ACTIVE.AUX.1 : ACTIVE must always be enclosed by NSELECT or PSELECT, such that no ACTIVE layer edge(s) coincide with NSELECT or PSELECT layer edge(s)", + ) + sramdrc.edges().and_(active).output( + "SRAM.ACTIVE.AUX.2", + "SRAM.ACTIVE.AUX.2 : ACTIVE not belonging to an SRAM cell may not touch the SRAMDRC layer", + ) + active.notch(mm(1), projection).with_angle(0).output( + "ACTIVE.AUX.3", + "ACTIVE.AUX.3 : notch in ACTIVE along vertical axis is not allowed", + ) + (active.interacting(gate) & nselect).sized(um(30)).not_interacting( + active & pselect - nwell + ).sized(-um(30)).output( + "ACTIVE.LUP.1", + "ACTIVE.LUP.1 : Maximum distance, to prevent latch-up, between ACTIVE forming a MOS device and ACTIVE forming a bulk/substrate contact within the same WELL/substrate : 30um", + ) + (active.interacting(gate) & pselect).sized(um(30)).not_interacting( + active & nselect & nwell + ).sized(-um(30)).output( + "ACTIVE.LUP.1", + "ACTIVE.LUP.1 : Maximum distance, to prevent latch-up, between ACTIVE forming a MOS device and ACTIVE forming a bulk/substrate contact within the same WELL/substrate : 30um", + ) + + ### GCUT + gcut.width(nm(17), projection).with_angle(0).output( + "GCUT.W.1", "GCUT.W.1 : Min. vertical width of GCUT : 17nm" + ) + gcut.separation(active, nm(4), projection).output( + "GCUT.ACTIVE.S.1", + "GCUT.ACTIVE.S.1 : Min. vertical spacing between GCUT and channel : 4nm", + ) + gcut.enclosing(gate.and_(gcut), nm(17), projection).with_angle( + 90 + ).polygons().without_area(0).output( + "GCUT.GATE.EX.1", + "GCUT.GATE.EX.1 : Min. horizontal extension of GCUT past GATE : 17nm", + ) + gcut.separation(gate, nm(17), projection).output( + "GCUT.GATE.S.2", "GCUT.GATE.S.2 : Minimum spacing of GCUT to GATE : 17nm" + ) + gcut.space(nm(35), projection).with_angle(0).output( + "GCUT.S.3", "GCUT.S.3 : Min. vertical spacing between GCUT : 35nm" + ) + gcut.not_interacting(gate).output( + "GCUT.AUX.1", "GCUT.AUX.1 : GCUT layer may not exist without the layer GATE" + ) + gcut.edges().with_angle(90).and_(gate).output( + "GCUT.AUX.2", + "GCUT.AUX.2 : GCUT layer vertical edge may not lie inside, or coincide with, the GATE layer", + ) + gcut.edges().and_(active).output( + "GCUT.AUX.3", "GCUT.AUX.3 : GCUT may not interact with channel" + ) + + ### NSELECT/PSELECT LVT/SLVT/SRAMVT + nselect.width(nm(108), projection).with_angle(90).output( + "NSELECT.W.1", "NSELECT.W.1 : Min. horizontal width of NSELECT : 108nm" + ) + nselect.width(nm(54), projection).with_angle(0).output( + "NSELECT.W.2", "NSELECT.W.2 : Min. vertical width of NSELECT : 54nm" + ) + nselect.enclosing(activenosram, nm(46), projection).with_angle(90).output( + "NSELECT.ACTIVE.EN.1", + "NSELECT.ACTIVE.EN.1 : Min. horizontal enclosure of ACTIVE by NSELECT : 46nm", + ) + nselect.enclosing(activenosram, nm(27), projection).with_angle(0).output( + "NSELECT.ACTIVE.EN.2", + "NSELECT.ACTIVE.EN.2 : Min. vertical enclosure of ACTIVE by NSELECT : 27nm", + ) + nselect.enclosing(activesram, nm(13.5), projection).output( + "SRAM.NSELECT.ACTIVE.EN.3-4", + "SRAM.NSELECT.ACTIVE.EN.3-4 : Min. enclosure of ACTIVE in SRAM by NSELECT : 13.5nm", + ) + nselect.enclosing(gatenogcut, nm(7), projection).with_angle( + 90 + ).polygons().without_area(0).output( + "NSELECT.GATE.EX.1-2", + "NSELECT.GATE.EX.1-2 : Min. horizontal extension of NSELECT past GATE : 7nm", + ) + pselect.width(nm(108), projection).with_angle(90).output( + "PSELECT.W.1", "PSELECT.W.1 : Min. horizontal width of PSELECT : 108nm" + ) + pselect.width(nm(54), projection).with_angle(0).output( + "PSELECT.W.2", "PSELECT.W.2 : Min. vertical width of PSELECT : 54nm" + ) + pselect.enclosing(activenosram, nm(46), projection).with_angle(90).output( + "PSELECT.ACTIVE.EN.1", + "PSELECT.ACTIVE.EN.1 : Min. horizontal enclosure of ACTIVE by PSELECT : 46nm", + ) + pselect.enclosing(activenosram, nm(27), projection).with_angle(0).output( + "PSELECT.ACTIVE.EN.2", + "PSELECT.ACTIVE.EN.2 : Min. vertical enclosure of ACTIVE by PSELECT : 27nm", + ) + pselect.enclosing(activesram, nm(13.5), projection).output( + "SRAM.PSELECT.ACTIVE.EN.3-4", + "SRAM.PSELECT.ACTIVE.EN.3-4 : Min. enclosure of ACTIVE in SRAM by PSELECT : 13.5nm", + ) + pselect.enclosing(gatenogcut, nm(7), projection).with_angle( + 90 + ).polygons().without_area(0).output( + "PSELECT.GATE.EX.1-2", + "PSELECT.GATE.EX.1-2 : Min. horizontal extension of PSELECT past GATE : 7nm", + ) + (nselect & pselect).output( + "NSELECT.PSELECT.AUX.1", + "NSELECT.PSELECT.AUX.1 : NSELECT and PSELECT may not overlap", + ) + ((lvt & slvt) + (lvt & sramvt) + (slvt & sramvt)).output( + "VT.AUX.2", "VT.AUX.2 : VT layers (LVT, SLVT, and SRAMVT) may not overlap" + ) + + ### SDT + sdt.width(nm(24), projection).with_angle(90).output( + "SDT.W.1", "SDT.W.1 : Min. horizontal width of SDT : 24nm" + ) + sdt.not_(sramdrc).width(nm(27), projection).with_angle(0).output( + "SDT.W.2", "SDT.W.2 : Min. vertical width of SDT : 27nm" + ) + sdt.without_bbox_height(nm(27)).and_(sdt.without_bbox_height(2 * nm(27))).and_( + sdt.without_bbox_height(3 * nm(27)) + ).and_(sdt.without_bbox_height(4 * nm(27))).and_( + sdt.without_bbox_height(5 * nm(27)) + ).and_( + sdt.without_bbox_height(6 * nm(27)) + ).and_( + sdt.without_bbox_height(7 * nm(27)) + ).and_( + sdt.without_bbox_height(8 * nm(27)) + ).and_( + sdt.without_bbox_height(9 * nm(27)) + ).and_( + sdt.without_bbox_height(10 * nm(27)) + ).and_( + sdt.without_bbox_height(11 * nm(27)) + ).and_( + sdt.without_bbox_height(12 * nm(27)) + ).output( + "SDT.W.3", + "SDT.W.3 : SDT vertical height increment is an integer multiple of : 27nm", + ) + sdt.and_(sramdrc).width(nm(17), projection).with_angle(0).output( + "SRAM.SDT.W.4", "SRAM.SDT.W.4 : Min. vertical width of SDT in SRAM : 17nm" + ) + sdt.space(nm(30), projection).with_angle(90).output( + "SDT.S.1", "SDT.S.1 : Min. vertical spacing between SDT : 30nm" + ) + sdt.separation(gate, nm(5), projection).output( + "SDT.GATE.S.2", + "SDT.GATE.S.2 : Min. horizontal spacing between SDT and GATE : 5nm", + ) + + ### LISD + lisd.width(nm(24)).output("LISD.W.1", "LISD.W.1 : Min. width of LISD : 24nm") + lisd.space(nm(18)).polygons().not_interacting( + lisd.edges().with_length((0, nm(36))) + ).output( + "LISD.S.1", + "LISD.S.1 : Min. spacing between two LISD' edges, when both edges are > 36 nm : 18nm", + ) + lisd.space(nm(25)).polygons().not_interacting( + lisd.edges().with_length((nm(36), mm(100))) + ).output( + "LISD.S.2", + "LISD.S.2 : Min. spacing between two LISD' edges, when one edge is > 36 nm : 25nm", + ) + lisd.space(nm(27), euclidian).polygons().not_interacting( + lisd.edges().with_length((nm(36), mm(100))) + ).output( + "LISD.S.3-4", + "LISD.S.3-4 : Min. spacing between two LISD' edges, when both edge are =< 36 nm : 27nm", + ) + lisd.with_area((0, 0.000648)).output( + "LISD.A.1", "LISD.A.1 : Min. area of LISD : 648nm2" + ) + lisd.and_(sramdrc).with_area(0).output( + "SRAM.LISD.AUX.1", + "SRAM.LISD.AUX.1 : LISD, which are either completely outside the SRAMDRC or which do not intersect the SRAMDRC, may not touch the SRAMDRC", + ) + + ### LIG + lig.width(nm(16)).output("LIG.W.1", "LIG.W.1 : Min. width of LIG : 16nm") + lig.space(nm(18)).polygons().not_interacting( + lig.edges().with_length((0, nm(36))) + ).output( + "LIG.S.1", + "LIG.S.1 : Min. spacing between two LIG' edges, when both edges are > 36 nm : 18nm", + ) + lig.space(nm(25)).polygons().not_interacting( + lig.edges().with_length((nm(36), mm(100))) + ).output( + "LISD.S.2", + "LIG.S.2 : Min. spacing between two LIG' edges, when one edge is > 36 nm : 25nm", + ) + lig.space(nm(27)).polygons().not_interacting( + lig.edges().with_length((0, nm(24))) + ).not_interacting(lig.edges().with_length((nm(36), mm(100)))).output( + "LIG.S.3", + "LIG.S.3 : Min. spacing between two LIG' edges, when both edges are >=24nm and =< 36 nm : 27nm", + ) + lig.space(nm(31), euclidian).polygons().not_interacting( + lisd.edges().with_length((nm(24), mm(100))) + ).output( + "LIG.S.4-5", + "LISD.S.4-5 : Min. spacing between two LIG' edges, when both edge are =< 24 nm : 31nm", + ) + lig.separation(lisd, nm(14), projection).output( + "LIG.LISD.S.6" + "LIG.LISD.S.6 : Min. spacing between LIG and LISD not on the same net : 14nm" + ) + lig.separation(lisd, nm(15), euclidian).output( + "LIG.LISD.S.7" + "LIG.LISD.S.7 : Min. corner-to-corner spacing between LISD and LIG not on the same net : 15nm" + ) + lig.separation(sdt, nm(14), projection).output( + "LIG.SDT.S.8" + "LIG.SDT.S.8 : Min. spacing between LIG and SDT not on the same net : 14nm" + ) + lig.separation(gatenogcut, nm(14), projection).with_angle(0).output( + "LIG.LISD.S.9A" + "LIG.LISD.S.9A : Min. vertical spacing between LIG and GATE (not cut using GCUT) : 14nm" + ) + lig.separation(gatenogcut, nm(17), projection).with_angle(90).output( + "LIG.LISD.S.9B" + "LIG.LISD.S.9B : Min. horizontal spacing between LIG and GATE (not cut using GCUT) : 17nm" + ) + lig.separation(gatenogcut & active, nm(5), euclidian).output( + "LIG.GATE.S.10" + "LIG.GATE.S.10 : Min. spacing between LIG and GATE (not cut using GCUT) forming a channel : 5nm" + ) + lig.separation(gcut, nm(5), euclidian).with_angle(0).output( + "LIG.GCUT.S.11" + "LIG.GCUT.S.11 : Min. vertical spacing between LIG and GCUT : 5nm" + ) + lig.with_area((0, 0.000324)).output( + "LIG.A.1", "LIG.A.1 : Min. area of LIG : 324nm2" + ) + (lig + lisd).with_area((0, 0.000128)).output( + "LIG.LISD.A.2", + "LIG.LISD.A.2 : Min. area of overlap between LIG and LISD : 128nm2", + ) + (lignosram + gatenogcut).with_area((0, 0.000320)).output( + "LIG.GATE.A.3", + "LIG.GATE.A.3 : Min. area of overlap between LIG and and GATE (not cut using GCUT) : 320nm2", + ) + (ligsram + gatenogcut).with_area((0, 0.000240)).output( + "LIG.GATE.A.4", + "LIG.GATE.A.4 : Min. area of overlap between LIG in SRAM and and GATE (not cut using GCUT) : 240nm2", + ) + lignosram.edges().with_angle(90).and_(gate).output( + "LIG.GATE.AUX.1", + "LIG.GATE.AUX.1 : LIG vertical edge may not lie inside, or coincide with, the GATE layer", + ) + ligsram.with_area(0).output( + "SRAM.LIG.AUX.2", + "SRAM.LIG.AUX.2 : LIG, which are either completely outside the SRAMDRC or which do not intersect the SRAMDRC, may not touch the SRAMDRC", + ) + lignosram.enclosing(gatenogcut, nm(1), projection).polygons().without_area( + 0 + ).output( + "LIG.GATE.EX.1", + "LIG.GATE.EX.1 : Min. extension of LIG past GATE (not cut using GCUT) : 1nm", + ) + (lig + lisd).width(nm(8)).output( + "LIG.LISD.OV.1", + "LIG.LISD.OV.1 : Min. overlap between LIG and LISD connected together : 8nm", + ) + (ligsram + gatenogcut).width(nm(15)).output( + "SRAM.LIG.GATE.OV.2", + "SRAM.LIG.GATE.OV.2 : Min. overlap between LIG in SRAM and GATE (not cut using GCUT) : 15nm", + ) + + ### V0 + # rule V0.W.1 not checked for the instance along the length of M1 + v0.width(nm(18)).output("V0.W.1", "V0.W.1 : Min. width of V0 : 18nm") + v0.space(nm(18), projection).output( + "V0.S.1", + "V0.S.1 : Min. spacing between V0 instances [on the same M1 track or on parallel M1 tracks, if they are fully or partially aligned with each other : 18nm", + ) + v0spaceortho = v0.space(nm(27), projection).polygons() + v0.space(nm(27), euclidian).polygons().not_interacting(v0spaceortho).output( + "V0.S.1", + "V0.S.1 : Min. spacing between V0 instances on parallel M1 tracks, if they are not aligned with each other : 27nm", + ) + m1ovlp5v0 = ( + (m1 & v0.sized(nm(5)) - v0) + .sized(-nm(2.5) + 1 * deck.dbu) + .sized(nm(2.5) - 1 * deck.dbu) + ) + v0.edges().and_(m1ovlp5v0).space(nm(23), euclidian).polygons().not_( + v0spaceortho + ).output( + "V0.S.2", + "V0.S.2 : Min. corner-to-corner spacing between two V0 instances—both with a 5 nm M1 end-cap : 23nm", + ) + v0.edges().not_interacting(m1ovlp5v0).space(nm(30), euclidian).output( + "V0.S.3", + "V0.S.3 : Min. corner-to-corner spacing between two V0 instances—both without a 5 nm M1 end-cap : 30nm", + ) + v0.edges().and_(m1ovlp5v0).separation( + v0.edges().not_interacting(m1ovlp5v0), nm(27), euclidian + ).output( + "V0.S.4", + "V0.S.4 : Min. corner-to-corner spacing between two V0 instances—one with and another without, a 5 nm M1 end-cap : 27nm", + ) + v0.not_interacting(m1ovlp5v0.sized(2 * deck.dbu)).output( + "V0.M1.EN.1", "V0.M1.EN.1 : enclosure of V0 by M1 on one side : 5nm" + ) + v0spaceortho.forget() + m1ovlp5v0.forget() + ( + lisd.enclosing( + v0.not_interacting(lig), nm(3), projection, two_connected_sides_allowed + ).edges() + - lisd.enclosing( + v0.not_interacting(lig), nm(3), projection, one_side_allowed + ).edges() + ).output( + "V0.LISD.EN.2", + "V0.LISD.EN.2 : Min. enclosure of V0 (interacting with LISD, but not with LIG) by LISD on at least two opposite sides is 3 nm and such a V0 must lie completely inside LISD : 3nm", + ) + lisd.enclosing(v0.interacting(lig), nm(3), projection, one_side_allowed).output( + "V0.LISD.EN.3", + "V0.LISD.EN.3 : Min. enclosure of V0 (sharing some, but not all its area with LISD) by LISD (interacting with LIG) on at least two opposite sides is 3 nm and such a V0 does not need to lie completely inside LISD : 3nm", + ) + ( + v0.enclosing(lig, nm(1), projection, two_connected_sides_allowed).edges() + - v0.enclosing(lig, nm(1), projection, one_side_allowed).edges() + ).not_(lig).output( + "V0.LIG.EN.4", + "V0.LIG.EN.4 : Min. enclosure of LIG by V0 (interacting with LIG and not inside LISD) on two opposite sides is 1 nm : 1nm", + ) + (v0 & lig).with_area((0, 0.000288)).output( + "V0.LIG.A.1", + "V0.LIG.A.1 : Min. area of overlap between V0 (interacting with LIG and not inside LISD) and LIG : 288nm2", + ) + ((v0 - m1) + (v0 - lig.sized(nm(1)) - lisd)).output( + "V0.AUX.1-2" "V0.AUX.1-2 : V0 must always interact with M1 and [LISD|LIG]" + ) + v0.not_interacting( + m1.enclosing(v0, 1 * deck.dbu, projection, two_opposite_sides_allowed).edges() + ).output( + "V0.M1.AUX.3", + "V0.M1.AUX.3 : V0 must exactly be the same width as M1 along the direction perpendicular to the M1 length", + ) + + ### M1 + m1.width(nm(18)).output("M1.W.1", "M1.W.1 : Min. width of M1 : 18nm") + m1.space(nm(18)).polygons().not_interacting( + m1.edges().with_length((0, nm(36))) + ).output( + "M1.S.1", + "M1.S.1 : Min. spacing between two M1' edges, when both edges are > 36 nm : 18nm", + ) + m1.space(nm(25)).polygons().not_interacting( + m1.edges().with_length((nm(36), mm(100))) + ).output( + " ", + "M1.S.2 : Min. spacing between two M1' edges, when one edge is > 36 nm : 25nm", + ) + m1.space(nm(27)).polygons().not_interacting( + m1.edges().with_length((0, nm(24))) + ).not_interacting(m1.edges().with_length((nm(36), mm(100)))).output( + "M1.S.3", + "M1.S.3 : Min. spacing between two M1' edges, when both edges are >=24nm and =< 36 nm : 27nm", + ) + m1.space(nm(31)).polygons().not_interacting( + m1.edges().with_length((nm(24), mm(100))) + ).output( + "M1.S.4-5", + "M1.S.4-5 : Min. spacing between two M1' edges, when both edge are =< 24 nm : 31nm", + ) + m1long = ( + m1.space(nm(20), projection) + .polygons() + .interacting(m1.edges().with_length((nm(36), mm(100)))) + ) + m1.space(nm(20), euclidian).polygons().not_interacting(m1long).output( + "M1.S.6", "M1.S.6 : Min. corner-to-corner spacing between two M1 : 20nm" + ) + m1long.forget() + m1.with_area((0, 0.000504)).output("M1.A.1", "M1.A.1 : Min. area of M1 : 504nm2") + + ### M2 + m2.width(nm(18)).output("M2.W.1", "M2.W.1 : Min. width of M2 : 18nm") + m2.space(nm(18)).polygons().not_interacting( + m2.edges().with_length((0, nm(36))) + ).output( + "M2.S.1", + "M2.S.1 : Min. spacing between two M2' edges, when both edges are > 36 nm : 18nm", + ) + m2.space(nm(25)).polygons().not_interacting( + m2.edges().with_length((nm(36), mm(100))) + ).output( + "M2.S.2", + "M2.S.2 : Min. spacing between two M2' edges, when one edge is > 36 nm : 25nm", + ) + m2.space(nm(27)).polygons().not_interacting( + m2.edges().with_length((0, nm(24))) + ).not_interacting(m2.edges().with_length((nm(36), mm(100)))).output( + "M2.S.3", + "M2.S.3 : Min. spacing between two M2' edges, when both edges are >=24nm and =< 36 nm : 27nm", + ) + m2.space(nm(31)).polygons().not_interacting( + m2.edges().with_length((nm(24), mm(100))) + ).output( + "M2.S.4-5", + "M2.S.4-5 : Min. spacing between two M2' edges, when both edge are =< 24 nm : 31nm", + ) + m2long = ( + m2.space(nm(20), projection) + .polygons() + .interacting(m2.edges().with_length((nm(36), mm(100)))) + ) + m2.space(nm(20), euclidian).polygons().not_interacting(m2long).output( + "M2.S.6", "M2.S.6 : Min. corner-to-corner spacing between two M2 : 20nm" + ) + m2long.forget() + m2.with_area((0, 0.000504)).output("M2.A.1", "M2.A.1 : Min. area of M2 : 504nm2") + + ### M3 + m3.width(nm(18)).output("M3.W.1", "M3.W.1 : Min. width of M3 : 18nm") + m3.space(nm(18)).polygons().not_interacting( + m3.edges().with_length((0, nm(36))) + ).output( + "M3.S.1", + "M3.S.1 : Min. spacing between two M3' edges, when both edges are > 36 nm : 18nm", + ) + m3.space(nm(25)).polygons().not_interacting( + m3.edges().with_length((nm(36), mm(100))) + ).output( + "M3.S.2", + "M3.S.2 : Min. spacing between two M3' edges, when one edge is > 36 nm : 25nm", + ) + m3.space(nm(27)).polygons().not_interacting( + m3.edges().with_length((0, nm(24))) + ).not_interacting(m3.edges().with_length((nm(36), mm(100)))).output( + "M3.S.3", + "M3.S.3 : Min. spacing between two M3' edges, when both edges are >=24nm and =< 36 nm : 27nm", + ) + m3.space(nm(31)).polygons().not_interacting( + m3.edges().with_length((nm(24), mm(100))) + ).output( + "M3.S.4-5", + "M3.S.4-5 : Min. spacing between two M3' edges, when both edge are =< 24 nm : 31nm", + ) + m3long = ( + m3.space(nm(20), projection) + .polygons() + .interacting(m3.edges().with_length((nm(36), mm(100)))) + ) + m3.space(nm(20), euclidian).polygons().not_interacting(m3long).output( + "M3.S.6", "M3.S.6 : Min. corner-to-corner spacing between two M3 : 20nm" + ) + m3long.forget() + m3.with_area((0, 0.000504)).output("M3.A.1", "M3.A.1 : Min. area of M3 : 504nm2") + + ### V1 + # rule V1.W.1 not checked for the instance along the length of M2 + v1.width(nm(18)).output("V1.W.1", "V1.W.1 : Min. width of V1 : 18nm") + v1.space(nm(18), projection).output( + "V1.S.1", + "V1.S.1 : Min. spacing between V1 instances [on the same M2 track or on parallel M2 tracks, if they are fully or partially aligned with each other : 18nm", + ) + v1spaceortho = v1.space(nm(27), projection).polygons() + v1.space(nm(27), projection).polygons().not_interacting(v1spaceortho).output( + "V1.S.1", + "V1.S.1 : Min. spacing between V1 instances on parallel M2 tracks, if they are not aligned with each other : 27nm", + ) + m2ovlp5v1 = ( + (m2 & v1.sized(nm(5) + 2 * deck.dbu) - v1).sized(-nm(2.5)).sized(nm(2.5)) + ) + v1.edges().and_(m2ovlp5v1).space(nm(23), euclidian).polygons().not_( + v1spaceortho + ).output( + "V1.S.2", + "V1.S.2 : Min. corner-to-corner spacing between two V1 instances—both with a 5 nm M2 end-cap : 23nm", + ) + v1.edges().not_interacting(m2ovlp5v1).space(nm(30), euclidian).output( + "V1.S.3", + "V1.S.3 : Min. corner-to-corner spacing between two V1 instances—both without a 5 nm M2 end-cap : 30nm", + ) + v1.edges().and_(m2ovlp5v1).separation( + v1.edges().not_interacting(m2ovlp5v1), nm(27), euclidian + ).output( + "V1.S.4", + "V1.S.4 : Min. corner-to-corner spacing between two V1 instances—one with and another without, a 5 nm M2 end-cap : 27nm", + ) + v1spaceortho.forget() + m1ovlp5v1 = ( + (m1 & v1.sized(nm(5)) - v1) + .sized(-nm(2.5) + 1 * deck.dbu) + .sized(nm(2.5) - 1 * deck.dbu) + ) + v1.interacting( + m1.enclosing(v1, nm(2), projection, two_connected_sides_allowed).edges() + - m1.enclosing(v1, nm(2), projection, one_side_allowed).edges() + ).output( + "V1.M1.EN.1", + "V1.M1.EN.1 : Min. enclosure of V1 by M1 on at least two opposite sides : 5nm & 2nm", + ) + v1.interacting( + m1.enclosing(v1, nm(5), projection, two_connected_sides_allowed).edges() + - m1.enclosing(v1, nm(5), projection, one_side_allowed).edges() + ).not_interacting(m1ovlp5v1.sized(1 * deck.dbu)).output( + "V1.M1.EN.1", + "V1.M1.EN.1 : Min. enclosure of V1 by M1 on at least two opposite sides : 5nm & 2nm", + ) + v1.not_interacting(m2ovlp5v1.sized(2 * deck.dbu)).output( + "V1.M2.EN.2", "V1.M2.EN.2 : enclosure of V1 by M2 on one side : 5nm" + ) + m1ovlp5v1.forget() + m2ovlp5v1.forget() + ((v1 - m2) + (v1 - m1)).output("V1.AUX.1" "V1.AUX.1 : V1 must be inside M1 and M2") + v1.not_interacting( + m2.enclosing(v1, 1 * deck.dbu, projection, two_opposite_sides_allowed).edges() + ).output( + "V1.M2.AUX.2", + "V1.M2.AUX.2 : V1 must exactly be the same width as M2 along the direction perpendicular to the M2 length", + ) + + ### V2 + # rule V2.W.1 not checked for the instance along the length of M3 + v2.width(nm(18)).output("V2.W.1", "V2.W.1 : Min. width of V2 : 18nm") + v2.space(nm(18), projection).output( + "V2.S.1", + "V2.S.1 : Min. spacing between V2 instances [on the same M3 track or on parallel M3 tracks, if they are fully or partially aligned with each other : 18nm", + ) + v2spaceortho = v2.space(nm(27), projection).polygons() + v2.space(nm(27), projection).polygons().not_interacting(v2spaceortho).output( + "V2.S.1", + "V2.S.1 : Min. spacing between V2 instances on parallel M3 tracks, if they are not aligned with each other : 27nm", + ) + m3ovlp5v2 = ( + (m3 & v2.sized(nm(5)) - v2).sized(-nm(2.5) + 1 * deck.dbu).sized(nm(2.5)) + ) + v2.edges().and_(m3ovlp5v2).space(nm(23), euclidian).polygons().not_( + v2spaceortho + ).output( + "V2.S.2", + "V2.S.2 : Min. corner-to-corner spacing between two V2 instances—both with a 5 nm M3 end-cap : 23nm", + ) + v2.edges().not_interacting(m3ovlp5v2).space(nm(30), euclidian).output( + "V2.S.3", + "V2.S.3 : Min. corner-to-corner spacing between two V2 instances—both without a 5 nm M3 end-cap : 30nm", + ) + v2.edges().and_(m3ovlp5v2).separation( + v2.edges().not_interacting(m3ovlp5v2), nm(27), euclidian + ).output( + "V2.S.4", + "V2.S.4 : Min. corner-to-corner spacing between two V2 instances—one with and another without, a 5 nm M3 end-cap : 27nm", + ) + v2.interacting( + m2.enclosing(v2, nm(5), projection, two_connected_sides_allowed).edges() + - m2.enclosing(v2, nm(5), projection, one_side_allowed).edges() + ).output( + "V2.M2.EN.1", + "V2.M2.EN.1 : Min. enclosure of V2 by M2 on at least two opposite sides : 5nm", + ) + v2.not_interacting(m3ovlp5v2.sized(2 * deck.dbu)).output( + "V2.M3.EN.2", "V2.M3.EN.2 : enclosure of V2 by M3 on one side : 5nm" + ) + m3ovlp5v2.forget() + ((v2 - m3) + (v2 - m2)).output("V2.AUX.1" "V2.AUX.1 : V2 must be inside M2 and M3") + v2.not_interacting( + m3.enclosing(v2, 1 * deck.dbu, projection, two_opposite_sides_allowed).edges() + ).output( + "V2.M3.AUX.2", + "V2.M3.AUX.2 : V2 must exactly be the same width as M3 along the direction perpendicular to the M3 length", + ) + + ### V3 + # rule V3.W.1 not checked for the instance along the length of M4 + v3.width(nm(18)).output("V3.W.1", "V3.W.1 : Min. width of V3 : 18nm") + v3.space(nm(18), projection).output( + "V3.S.1", + "V3.S.1 : Min. spacing between V3 instances [on the same M4 track or on parallel M4 tracks, if they are fully or partially aligned with each other : 18nm", + ) + v3spaceortho = v3.space(nm(27), projection).polygons() + v3.space(nm(27), projection).polygons().not_interacting(v3spaceortho).output( + "V3.S.1", + "V3.S.1 : Min. spacing between V3 instances on parallel M4 tracks, if they are not aligned with each other : 27nm", + ) + m4ovlp5v3 = ( + (m4 & v3.sized(nm(5)) - v3).sized(-nm(2.5) + 1 * deck.dbu).sized(nm(2.5)) + ) + v3.edges().and_(m4ovlp5v3).space(nm(23), euclidian).polygons().not_( + v3spaceortho + ).output( + "V3.S.2", + "V3.S.2 : Min. corner-to-corner spacing between two V3 instances—both with a 5 nm M4 end-cap : 23nm", + ) + v3.edges().not_interacting(m4ovlp5v3).space(nm(30), euclidian).output( + "V3.S.3", + "V3.S.3 : Min. corner-to-corner spacing between two V3 instances—both without a 5 nm M4 end-cap : 30nm", + ) + v3.edges().and_(m4ovlp5v3).separation( + v3.edges().not_interacting(m4ovlp5v3), nm(27), euclidian + ).output( + "V3.S.4", + "V3.S.4 : Min. corner-to-corner spacing between two V3 instances—one with and another without, a 5 nm M4 end-cap : 27nm", + ) + m4ovlp5v3.forget() + v3.interacting( + m3.enclosing(v3, nm(5), projection, two_connected_sides_allowed).edges() + - m3.enclosing(v3, nm(5), projection, one_side_allowed).edges() + ).output( + "V3.M3.EN.1", + "V3.M3.EN.1 : Min. enclosure of V3 by M3 on at least two opposite sides : 5nm", + ) + v3.interacting( + m4.enclosing(v3, nm(11), projection, two_connected_sides_allowed).edges() + - m4.enclosing(v3, nm(11), projection, one_side_allowed).edges() + ).output( + "V3.M4.EN.2", + "V3.M4.EN.2 : Min. enclosure of V3 by M4 on at least two opposite sides : 11nm", + ) + ((v3 - m4) + (v3 - m3)).output("V3.AUX.1" "V3.AUX.1 : V3 must be inside M3 and M4") + v3.not_interacting( + m4.enclosing(v3, 1 * deck.dbu, projection, two_opposite_sides_allowed) + .edges() + .with_angle(0) + ).output( + "V3.M4.AUX.2", + "V3.M4.AUX.2 : V3 must exactly be the same width as M4 along the direction perpendicular to the M4 length", + ) + + ### M4 + m4.width(nm(24), projection).with_angle(0).output( + "M4.W.1", "M4.W.1 : Min. vertical width of M4 : 24nm" + ) + m4.sized(0, -nm(240)).sized(0, nm(240)).output( + "M4.W.2", "M4.W.2 : Max. vertical width of M4 : 480nm" + ) + m4vedg = m4.width(nm(481), projection).edges().with_angle(90) + ( + m4vedg.with_length(nm(48)) + + m4vedg.with_length(nm(96)) + + m4vedg.with_length(nm(144)) + + m4vedg.with_length(nm(192)) + + m4vedg.with_length(nm(240)) + + m4vedg.with_length(nm(288)) + + m4vedg.with_length(nm(336)) + + m4vedg.with_length(nm(384)) + + m4vedg.with_length(nm(432)) + + m4vedg.with_length(nm(480)) + ).output( + "M4.W.3", + "M4.W.3 : M4 vertical width may not be an even integer multiple of its minimum width", + ) + # rule M4.W.4 not coded + m4.width(nm(44), projection).edges().with_angle(90).output( + "M4.W.5", "M4.W.5 : Min. horizontal width of M4 : 44nm" + ) + m4.space(nm(24), projection).edges().with_angle(0).output( + "M4.S.1", "M4.S.1 : Min. vertical spacing between M4 : 24nm" + ) + m4s2 = m4.space(nm(40), projection).edges().with_angle(90) + m4s2.output("M4.S.2", "M4.S.2 : Min. horizontal spacing between M4 : 40nm") + m4.sized(0, nm(48)).space(nm(40), projection).edges().with_angle(90).not_( + m4s2 + ).and_(m4).output( + "M4.S.3", + "M4.S.3 : Min. horizontal tip-to-tip spacing between two M4 on adjacent tracks : 40nm", + ) + m4.sized(0, nm(48)).notch(nm(40), projection).edges().with_angle(90).not_( + m4s2 + ).and_(m4).output( + "M4.S.4", + "M4.S.4 : Min. horizontal tip-to-tip spacing between two M4 on adjacent tracks : 40nm", + ) + m4.space(nm(25), projection).edges().with_angle(0).with_length((0, nm(44))).output( + "M4.S.5", + "M4.S.5 : Min. parallel run length of two M4 on adjacent tracks : 44nm", + ) + # rule M4.AUX.1 coded in ONGRID section + # rule M4.AUX.2 not coded + m4.corners((0, 90)).sized(nm(1)).output("M4.AUX.3", "M4.AUX.3 : M4 may not bend") + # rule M4.AUX.4 not coded because the routing track is not defined + + ### M5 + m5.width(nm(24), projection).with_angle(90).output( + "M5.W.1", "M5.W.1 : Min. horizontal width of M5 : 24nm" + ) + m5.sized(-nm(240), 0).sized(nm(240), 0).output( + "M5.W.2", "M5.W.2 : Max. horizontal width of M5 : 480nm" + ) + m5vedg = m5.width(nm(481), projection).edges().with_angle(0) + ( + m5vedg.with_length(nm(48)) + + m5vedg.with_length(nm(96)) + + m5vedg.with_length(nm(144)) + + m5vedg.with_length(nm(192)) + + m5vedg.with_length(nm(240)) + + m5vedg.with_length(nm(288)) + + m5vedg.with_length(nm(336)) + + m5vedg.with_length(nm(384)) + + m5vedg.with_length(nm(432)) + + m5vedg.with_length(nm(480)) + ).output( + "M5.W.3", + "M5.W.3 : M5 horizontal width may not be an even integer multiple of its minimum width", + ) + # rule M5.W.4 not coded + m5.width(nm(44), projection).edges().with_angle(0).output( + "M5.W.5", "M5.W.5 : Min. vertical width of M5 : 44nm" + ) + m5.space(nm(24), projection).edges().with_angle(90).output( + "M5.S.1", "M5.S.1 : Min. horizontal spacing between M5 : 24nm" + ) + m5s2 = m5.space(nm(40), projection).edges().with_angle(0) + m5s2.output("M5.S.2", "M5.S.2 : Min. vertical spacing between M5 : 40nm") + m5.sized(0, nm(48)).space(nm(40), projection).edges().with_angle(0).not_(m5s2).and_( + m5 + ).output( + "M5.S.3", + "M5.S.3 : Min. vertical tip-to-tip spacing between two M5 on adjacent tracks : 40nm", + ) + m5.sized(0, nm(48)).notch(nm(40), projection).edges().with_angle(0).not_(m5s2).and_( + m5 + ).output( + "M5.S.4", + "M5.S.4 : Min. vertical tip-to-tip spacing between two M5 on adjacent tracks : 40nm", + ) + m5.space(nm(25), projection).edges().with_angle(90).with_length((0, nm(44))).output( + "M5.S.5", + "M5.S.5 : Min. parallel run length of two M5 on adjacent tracks : 44nm", + ) + # rule M5.AUX.1 coded in ONGRID section + # rule M5.AUX.2 not coded + m5.corners((0, 90)).sized(nm(1)).output("M5.AUX.3", "M5.AUX.3 : M5 may not bend") + # rule M5.AUX.4 not coded because the routing track is not defined + + ### V4 + # rule V4.W.1 not checked for the instance along the length of M5 + v4.width(nm(24)).output("V4.W.1", "V4.W.1 : Min. width of V4 : 24nm") + v4.space(nm(33), euclidian).output( + "V4.S.1-2-3", "V4.S.1-2-3 : Min. spacing between V4 : 33nm" + ) + v4.interacting( + m4.enclosing(v4, nm(11), projection, two_connected_sides_allowed).edges() + - m4.enclosing(v4, nm(11), projection, one_side_allowed).edges() + ).output( + "V4.M4.EN.1", + "V4.M4.EN.1 : Min. enclosure of V4 by M4 on at least two opposite sides : 11nm", + ) + ((v4 - m4) + (v4 - m5)).output("V4.AUX.1" "V4.AUX.1 : V4 must be inside M4 and M5") + v4.not_interacting( + m5.enclosing(v4, 1 * deck.dbu, projection, two_opposite_sides_allowed) + .edges() + .with_angle(90) + ).output( + "V4.M5.AUX.2", + "V4.M5.AUX.2 : V4 must exactly be the same width as M5 along the direction perpendicular to the M5 length", + ) + + ### V5 + # rule V5.W.1 not checked for the instance along the length of M6 + v5.width(nm(24)).output("V5.W.1", "V5.W.1 : Min. width of V5 : 24nm") + v5.space(nm(33), euclidian).output( + "V5.S.1-2-3", "V5.S.1-2-3 : Min. spacing between V5 : 33nm" + ) + v5.interacting( + m5.enclosing(v5, nm(11), projection, two_connected_sides_allowed).edges() + - m5.enclosing(v5, nm(11), projection, one_side_allowed).edges() + ).output( + "V5.M5.EN.1", + "V5.M5.EN.1 : Min. enclosure of V5 by M5 on at least two opposite sides : 11nm", + ) + ((v5 - m5) + (v5 - m6)).output("V5.AUX.1" "V5.AUX.1 : V5 must be inside M5 and M6") + v5.not_interacting( + m6.enclosing(v5, 1 * deck.dbu, projection, two_opposite_sides_allowed) + .edges() + .with_angle(90) + ).output( + "V5.M6.AUX.2", + "V5.M6.AUX.2 : V5 must exactly be the same width as M6 along the direction perpendicular to the M6 length", + ) + + ### M6 + m6.width(nm(32), projection).with_angle(0).output( + "M6.W.1", "M6.W.1 : Min. vertical width of M6 : 32nm" + ) + m6.sized(0, -nm(320)).sized(0, nm(320)).output( + "M6.W.2", "M6.W.2 : Max. vertical width of M6 : 640nm" + ) + m6vedg = m6.width(nm(641), projection).edges().with_angle(90) + ( + m6vedg.with_length(nm(64)) + + m6vedg.with_length(nm(128)) + + m6vedg.with_length(nm(192)) + + m6vedg.with_length(nm(256)) + + m6vedg.with_length(nm(320)) + + m6vedg.with_length(nm(384)) + + m6vedg.with_length(nm(448)) + + m6vedg.with_length(nm(512)) + + m6vedg.with_length(nm(576)) + + m6vedg.with_length(nm(640)) + ).output( + "M6.W.3", + "M6.W.3 : M6 vertical width may not be an even integer multiple of its minimum width", + ) + # rule M6.W.4 not coded + m6.width(nm(44), projection).edges().with_angle(90).output( + "M6.W.5", "M6.W.5 : Min. horizontal width of M6 : 44nm" + ) + m6.space(nm(32), projection).edges().with_angle(0).output( + "M6.S.1", "M6.S.1 : Min. vertical spacing between M6 : 32nm" + ) + m6s2 = m6.space(nm(40), projection).edges().with_angle(90) + m6s2.output("M6.S.2", "M6.S.2 : Min. horizontal spacing between M6 : 40nm") + m6.sized(0, nm(48)).space(nm(40), projection).edges().with_angle(90).not_( + m6s2 + ).and_(m6).output( + "M6.S.3", + "M6.S.3 : Min. horizontal tip-to-tip spacing between two M6 on adjacent tracks : 40nm", + ) + m6.sized(0, nm(48)).notch(nm(40), projection).edges().with_angle(90).not_( + m6s2 + ).and_(m6).output( + "M6.S.4", + "M6.S.4 : Min. horizontal tip-to-tip spacing between two M6 on adjacent tracks : 40nm", + ) + m6.space(nm(25), projection).edges().with_angle(0).with_length((0, nm(44))).output( + "M6.S.5", + "M6.S.5 : Min. parallel run length of two M6 on adjacent tracks : 44nm", + ) + # rule M6.AUX.1 coded in ONGRID section + # rule M6.AUX.2 not coded + m6.corners((0, 90)).sized(nm(1)).output("M6.AUX.3", "M6.AUX.3 : M6 may not bend") + # rule M6.AUX.4 not coded because the routing track is not defined + + ### M7 + m7.width(nm(32), projection).with_angle(90).output( + "M7.W.1", "M7.W.1 : Min. horizontal width of M7 : 32nm" + ) + m7.sized(-nm(320), 0).sized(nm(320), 0).output( + "M7.W.2", "M7.W.2 : Max. horizontal width of M7 : 640nm" + ) + m7vedg = m7.width(nm(641), projection).edges().with_angle(0) + ( + m7vedg.with_length(nm(64)) + + m7vedg.with_length(nm(128)) + + m7vedg.with_length(nm(192)) + + m7vedg.with_length(nm(256)) + + m7vedg.with_length(nm(320)) + + m7vedg.with_length(nm(384)) + + m7vedg.with_length(nm(448)) + + m7vedg.with_length(nm(512)) + + m7vedg.with_length(nm(576)) + + m7vedg.with_length(nm(640)) + ).output( + "M7.W.3", + "M7.W.3 : M7 horizontal width may not be an even integer multiple of its minimum width", + ) + # rule M7.W.4 not coded + m7.width(nm(44), projection).edges().with_angle(0).output( + "M7.W.5", "M7.W.5 : Min. vertical width of M7 : 44nm" + ) + m7.space(nm(32), projection).edges().with_angle(90).output( + "M7.S.1", "M7.S.1 : Min. horizontal spacing between M7 : 32nm" + ) + m7s2 = m7.space(nm(40), projection).edges().with_angle(0) + m7s2.output("M7.S.2", "M7.S.2 : Min. vertical spacing between M7 : 40nm") + m7.sized(0, nm(48)).space(nm(40), projection).edges().with_angle(0).not_(m7s2).and_( + m7 + ).output( + "M7.S.3", + "M7.S.3 : Min. vertical tip-to-tip spacing between two M7 on adjacent tracks : 40nm", + ) + m7.sized(0, nm(48)).notch(nm(40), projection).edges().with_angle(0).not_(m7s2).and_( + m7 + ).output( + "M7.S.4", + "M7.S.4 : Min. vertical tip-to-tip spacing between two M7 on adjacent tracks : 40nm", + ) + m7.space(nm(25), projection).edges().with_angle(90).with_length((0, nm(44))).output( + "M7.S.5", + "M7.S.5 : Min. parallel run length of two M7 on adjacent tracks : 44nm", + ) + # rule M7.AUX.1 coded in ONGRID section + # rule M7.AUX.2 not coded + m7.corners((0, 90)).sized(nm(1)).output("M7.AUX.3", "M7.AUX.3 : M7 may not bend") + # rule M7.AUX.4 not coded because the routing track is not defined + + ### V6 + # rule V6.W.1 not checked for the instance along the length of M7 + v6.width(nm(32)).output("V6.W.1", "V6.W.1 : Min. width of V6 : 32nm") + v6.space(nm(45), euclidian).output( + "V6.S.1-2-3", "V6.S.1-2-3 : Min. spacing between V6 : 45nm" + ) + v6.interacting( + m6.enclosing(v6, nm(11), projection, two_connected_sides_allowed).edges() + - m6.enclosing(v6, nm(11), projection, one_side_allowed).edges() + ).output( + "V6.M6.EN.1", + "V6.M6.EN.1 : Min. enclosure of V6 by M6 on at least two opposite sides : 11nm", + ) + ((v6 - m6) + (v6 - m7)).output("V6.AUX.1" "V6.AUX.1 : V6 must be inside M6 and M7") + v6.not_interacting( + m7.enclosing(v6, 1 * deck.dbu, projection, two_opposite_sides_allowed) + .edges() + .with_angle(90) + ).output( + "V6.M7.AUX.2", + "V6.M7.AUX.2 : V6 must exactly be the same width as M7 along the direction perpendicular to the M7 length", + ) + + ### V7 + # rule V7.W.1 not checked for the instance along the length of M8 + v7.width(nm(32)).output("V7.W.1", "V7.W.1 : Min. width of V7 : 32nm") + v7.space(nm(45), euclidian).output( + "V7.S.1-2-3", "V7.S.1-2-3 : Min. spacing between V7 : 45nm" + ) + v7.interacting( + m7.enclosing(v7, nm(11), projection, two_connected_sides_allowed).edges() + - m7.enclosing(v7, nm(11), projection, one_side_allowed).edges() + ).output( + "V7.M7.EN.1", + "V7.M7.EN.1 : Min. enclosure of V7 by M7 on at least two opposite sides : 11nm", + ) + ((v7 - m7) + (v7 - m8)).output("V7.AUX.1" "V7.AUX.1 : V7 must be inside M7 and M8") + v7.not_interacting( + m8.enclosing(v7, 1 * deck.dbu, projection, two_opposite_sides_allowed) + .edges() + .with_angle(90) + ).output( + "V7.M8.AUX.2", + "V7.M8.AUX.2 : V7 must exactly be the same width as M8 along the direction perpendicular to the M8 length", + ) + + ### M8 + m8.width(nm(40)).output("M8.W.1", "M8.W.1 : Min. width of M8 : 40nm") + m8.width(nm(60), projection).edges().with_length((0, nm(400))).output( + "M8.W.2", + "M8.W.2 : Min. width of M8, when its length >= 400 nm and < 1200 nm : 60nm", + ) + m8.width(nm(80), projection).edges().with_length((nm(400), nm(1200))).output( + "M8.W.3", + "M8.W.3 : Min. width of M8, when its length >= 1200 nm and < 1800 nm : 80nm", + ) + m8.width(nm(120), projection).edges().with_length((nm(1200), nm(1800))).output( + "M8.W.4", "M8.W.3 : Min. width of M8, when its length >=1800 nm : 120nm" + ) + m8.sized(-um(1)).sized(-um(1)).output( + "M8.W.5", "M8.W.5 : Max. width of M8 : 2000nm" + ) + m8.space(nm(40), projection).edges().with_length((nm(80), mm(100))).output( + "M8.S.1", + "M8.S.1 : Min. spacing between two M8' edges, when both edges are >= 80 nm : 40nm", + ) + m8.space(nm(43), projection).polygons().not_interacting( + m8.edges().with_length((nm(80), mm(100))) + ).output( + "M8.S.2", + "M8.S.2 : Min. spacing between two M8' edges, when one of the edges is < 80 nm and the other is >= 80 nm : 43nm", + ) + m8.space(nm(46), projection).edges().with_length((0, nm(80))).output( + "M8.S.3", + "M8.S.3 : Min. spacing between two M8' edges, when both edges are < 80 nm : 46nm", + ) + m8.space(nm(60), projection).polygons().interacting( + m8.edges().with_length((nm(60), nm(80))) + ).output( + "M8.S.4", + "M8.S.4 : Min. spacing between two M8' edges, when one of the edges is >= 60 nm and < 80 nm : 60nm", + ) + m8.space(nm(80), projection).polygons().interacting( + m8.edges().with_length((nm(80), nm(120))) + ).output( + "M8.S.5", + "M8.S.5 : Min. spacing between two M8' edges, when one of the edges is >= 80 nm and < 120 nm : 80nm", + ) + m8.space(nm(120), projection).polygons().interacting( + m8.edges().with_length((nm(120), nm(500))) + ).output( + "M8.S.6", + "M8.S.6 : Min. spacing between two M8' edges, when one of the edges is >= 120 nm and < 500 nm : 120nm", + ) + m8.space(nm(500), projection).polygons().interacting( + m8.edges().with_length((nm(500), nm(1000))) + ).output( + "M8.S.7", + "M8.S.7 : Min. spacing between two M8' edges, when one of the edges is >= 500 nm and < 1000 nm : 500nm", + ) + m8.space(nm(1000), projection).polygons().interacting( + m8.edges().with_length((nm(1000), mm(100))) + ).output( + "M8.S.8", + "M8.S.8 : Min. spacing between two M8' edges, when one of the edges is >= 1000 nm : 1000nm", + ) + m8.with_area((0, 0.007520)).output("M8.A.1", "M8.A.1 : Min. area of M8 : 7520nm2") + m8.edges().without_length((nm(40), mm(100))).output( + "M8.L.1", "M8.L.1 : Minimum feature length of M8 : 40nm" + ) + + ### M9 + m9.width(nm(40)).output("M9.W.1", "M9.W.1 : Min. width of M9 : 40nm") + m9.width(nm(60), projection).edges().with_length((0, nm(400))).output( + "M9.W.2", + "M9.W.2 : Min. width of M9, when its length >= 400 nm and < 1200 nm : 60nm", + ) + m9.width(nm(80), projection).edges().with_length((nm(400), nm(1200))).output( + "M9.W.3", + "M9.W.3 : Min. width of M9, when its length >= 1200 nm and < 1800 nm : 80nm", + ) + m9.width(nm(120), projection).edges().with_length((nm(1200), nm(1800))).output( + "M9.W.4", "M9.W.3 : Min. width of M9, when its length >=1800 nm : 120nm" + ) + m9.sized(-um(1)).sized(-um(1)).output( + "M9.W.5", "M9.W.5 : Max. width of M9 : 2000nm" + ) + m9.space(nm(40), projection).edges().with_length((nm(80), mm(100))).output( + "M9.S.1", + "M9.S.1 : Min. spacing between two M9' edges, when both edges are >= 80 nm : 40nm", + ) + m9.space(nm(43), projection).polygons().not_interacting( + m9.edges().with_length((nm(80), mm(100))) + ).output( + "M9.S.2", + "M9.S.2 : Min. spacing between two M9' edges, when one of the edges is < 80 nm and the other is >= 80 nm : 43nm", + ) + m9.space(nm(46), projection).edges().with_length((0, nm(80))).output( + "M9.S.3", + "M9.S.3 : Min. spacing between two M9' edges, when both edges are < 80 nm : 46nm", + ) + m9.space(nm(60), projection).polygons().interacting( + m9.edges().with_length((nm(60), nm(80))) + ).output( + "M9.S.4", + "M9.S.4 : Min. spacing between two M9' edges, when one of the edges is >= 60 nm and < 80 nm : 60nm", + ) + m9.space(nm(80), projection).polygons().interacting( + m9.edges().with_length((nm(80), nm(120))) + ).output( + "M9.S.5", + "M9.S.5 : Min. spacing between two M9' edges, when one of the edges is >= 80 nm and < 120 nm : 80nm", + ) + m9.space(nm(120), projection).polygons().interacting( + m9.edges().with_length((nm(120), nm(500))) + ).output( + "M9.S.6", + "M9.S.6 : Min. spacing between two M9' edges, when one of the edges is >= 120 nm and < 500 nm : 120nm", + ) + m9.space(nm(500), projection).polygons().interacting( + m9.edges().with_length((nm(500), nm(1000))) + ).output( + "M9.S.7", + "M9.S.7 : Min. spacing between two M9' edges, when one of the edges is >= 500 nm and < 1000 nm : 500nm", + ) + m9.space(nm(1000), projection).polygons().interacting( + m9.edges().with_length((nm(1000), mm(100))) + ).output( + "M9.S.8", + "M9.S.8 : Min. spacing between two M9' edges, when one of the edges is >= 1000 nm : 1000nm", + ) + m9.with_area((0, 0.007520)).output("M9.A.1", "M9.A.1 : Min. area of M9 : 7520nm2") + m9.edges().without_length((nm(40), mm(100))).output( + "M9.L.1", "M9.L.1 : Minimum feature length of M9 : 40nm" + ) + + ### V8 + ( + (v8.edges().without_length(nm(40)) & v8.edges().without_length(nm(120))) + + v8.with_area(0.0144).edges() + ).output("V8.W.1", "V8.W.1 : Exact width of a V8 instance") + v8.space(nm(57)).output("V8.S.1-2", "V8.S.1-2 : Min. spacing between V8 : 57.nm") + v8.interacting( + m8.enclosing(v8, nm(20), projection, two_connected_sides_allowed).edges() + - m8.enclosing(v8, nm(20), projection, one_side_allowed).edges() + ).output( + "V8.M8.EN.1", + "V8.M8.EN.1 : Min. enclosure of V8 by M8 on at least two opposite sides : 20nm", + ) + v8.interacting( + m9.enclosing(v8, nm(20), projection, two_connected_sides_allowed).edges() + - m9.enclosing(v8, nm(20), projection, one_side_allowed).edges() + ).output( + "V8.M9.EN.2", + "V8.M9.EN.2 : Min. enclosure of V8 by M9 on at least two opposite sides : 20nm", + ) + ((v8 - m8) + (v8 - m9)).output("V8.AUX.1" "V8.AUX.1 : V8 must be inside M8 and M9") + + ### V9 + ( + (v9.edges().without_length(nm(40)) & v9.edges().without_length(nm(120))) + + v9.with_area(0.0144).edges() + ).output("V9.W.1", "V9.W.1 : Exact width of a V9 instance") + v9.space(nm(57)).output("V9.S.1-2", "V9.S.1-2 : Min. spacing between V9 : 57.nm") + v9.interacting( + m9.enclosing(v9, nm(20), projection, two_connected_sides_allowed).edges() + - m9.enclosing(v9, nm(20), projection, one_side_allowed).edges() + ).output( + "V9.M9.EN.1", + "V9.M9.EN.1 : Min. enclosure of V9 by M9 on at least two opposite sides : 20nm", + ) + v9.interacting( + pad.enclosing(v9, nm(20), projection, two_connected_sides_allowed).edges() + - pad.enclosing(v9, nm(20), projection, one_side_allowed).edges() + ).output( + "V9.PAD.EN.2", + "V9.PAD.EN.2 : Min. enclosure of V9 by PAD on at least two opposite sides : 20nm", + ) + ((v9 - m9) + (v9 - pad)).output( + "V9.AUX.1" "V9.AUX.1 : V9 must be inside M9 and PAD" + ) + + # ONGRID defined in the paper : + # "ASAP7 : A 7-nm finFET predictive process design kit" + ########################################################## + if OFFGRID: + deck.info("GRID section") + + # special grids for M4.to M7 + m4.ongrid(nm(24), 0).output( + "M4.AUX.1", "M4.AUX.1 : M4 horizontal edges must be at a grid of : 24nm" + ) + m5.ongrid(0, nm(24)).output( + "M5.AUX.1", "M5.AUX.1 : M5 vertical edges must be at a grid of : 24nm" + ) + m6.ongrid(nm(32), 0).output( + "M4.AUX.1", "M4.AUX.1 : M4 horizontal edges must be at a grid of : 32nm" + ) + m7.ongrid(0, nm(32)).output( + "M5.AUX.1", "M5.AUX.1 : M5 vertical edges must be at a grid of : 32nm" + ) + + grid = nm(1) + all_drawing = dict( + nwell=nwell, + fin=fin, + gate=gate, + gcut=gcut, + active=active, + sdt=sdt, + nselect=nselect, + pselect=pselect, + slvt=slvt, + lvt=lvt, + sramdrc=sramdrc, + sramvt=sramvt, + dummy=dummy, + lig=lig, + lisd=lisd, + v0=v0, + m1=m1, + v1=v1, + m2=m2, + v2=v2, + m3=m3, + v3=v3, + m4=m4, + v4=v4, + m5=m5, + v5=v5, + m6=m6, + v6=v6, + m7=m7, + v7=v7, + m8=m8, + v8=v8, + m9=m9, + v9=v9, + ) + for dwg, layer in all_drawing.items(): + layer.ongrid(grid).polygons(nm(2)).output( + "GRID: vertexes on layer %s not on grid of %.12g" % (dwg, grid) + ) + nonortho_edges = ( + layer.edges() + - layer.edges().with_angle(0) + - layer.edges().with_angle(90) + ) + nonortho_edges.output( + "%s.GEOMETRY.NONORTHOGONAL" % dwg, + "GEOMETRY.NONORTHOGONAL on layer %s" % dwg, + ) diff --git a/flow/test/test_klayout_drc.py b/flow/test/test_klayout_drc.py new file mode 100644 index 0000000000..f95a0109fa --- /dev/null +++ b/flow/test/test_klayout_drc.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""klayout_drc.py: each check flags the shapes that break it, and only those. + +Runs on the klayout Python module, and is skipped without it. +""" + +import os +import sys +import tempfile +import unittest + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../util")) + +try: + import klayout.db as db +except ImportError: + db = None + +import klayout_drc +from klayout_drc import nm, projection, um + + +def write_gds(path, boxes): + """boxes: (layer, datatype, x1, y1, x2, y2) in microns, in a hierarchy of + a top cell and one child cell placed twice.""" + layout = db.Layout() + layout.dbu = 0.001 + top = layout.create_cell("TOP") + child = layout.create_cell("CHILD") + for layer, datatype, x1, y1, x2, y2 in boxes: + li = layout.layer(layer, datatype) + top.shapes(li).insert(db.DBox(x1, y1, x2, y2)) + li = layout.layer(1, 0) + child.shapes(li).insert(db.DBox(0, 0, 0.05, 1)) # narrow: one violation + top.insert(db.DCellInstArray(child.cell_index(), db.DTrans(db.DVector(100, 0)))) + top.insert(db.DCellInstArray(child.cell_index(), db.DTrans(db.DVector(200, 0)))) + layout.write(path) + + +@unittest.skipIf(db is None, "the klayout Python module is not installed") +class TestKlayoutDrc(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.gds = os.path.join(self.tmp.name, "in.gds") + self.rdb = os.path.join(self.tmp.name, "out.lyrdb") + write_gds( + self.gds, + [ + (1, 0, 0, 0, 0.08, 1), # width 80 nm + (1, 0, 1, 0, 1.2, 1), # clean + (1, 0, 1.25, 0, 1.45, 1), # 50 nm from the one above + (2, 0, 3, 0, 3.1, 0.1), # via + (1, 0, 2.99, -0.01, 3.11, 0.11), # 10 nm enclosure + (2, 0, 4, 0, 4.1, 0.1), # via + (1, 0, 3.9, -0.1, 4.2, 0.2), # 100 nm enclosure + ], + ) + + def tearDown(self): + self.tmp.cleanup() + + def run_deck(self, body, mode="deep"): + deck = klayout_drc.Deck(self.gds, self.rdb, "test", mode=mode) + body(deck) + deck.write() + counts = {} + for name, cat in deck.categories.items(): + counts[name] = cat.num_items() + return counts + + def test_width_space_enclosing(self): + def body(deck): + m1 = deck.input(1, 0) + via = deck.input(2, 0) + m1.width(nm(100), projection).output("W", "width") + m1.space(nm(100), projection).output("S", "space") + m1.enclosing(via, nm(50), projection).output("E", "enclosure") + + counts = self.run_deck(body) + # The child cell's narrow shape is reported once, in the child. + self.assertEqual(counts, {"W": 2, "S": 1, "E": 4}) + + def test_flat_reports_each_instance(self): + def body(deck): + deck.input(1, 0).width(nm(100), projection).output("W") + + self.assertEqual(self.run_deck(body, mode="flat"), {"W": 3}) + + def test_universal_not_equal(self): + def body(deck): + m1 = deck.input(1, 0) + m1.drc(klayout_drc.width(projection) != nm(200)).with_angle(90).output("X") + + # Every edge of a shape that is not 200 nm wide, both sides: the + # 80 nm, 50 nm (twice in the child) and 300 nm wide shapes. + counts = self.run_deck(body) + self.assertGreater(counts["X"], 0) + + def test_area_and_units(self): + def body(deck): + self.assertEqual(deck.v(nm(80)), 80) + self.assertEqual(deck.v(um(1)), 1000) + self.assertEqual(deck.area(0.5), 500000) + deck.input(1, 0).with_area(0, 0.1).output("A") + + # 0.08, 0.0144 and 0.09 um2 in the top cell, 0.05 um2 once in the child + self.assertEqual(self.run_deck(body), {"A": 4}) + + +if __name__ == "__main__": + unittest.main() diff --git a/flow/test/test_klayout_sh.py b/flow/test/test_klayout_sh.py index 2bf841f1d1..40ace239c9 100644 --- a/flow/test/test_klayout_sh.py +++ b/flow/test/test_klayout_sh.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -"""klayout.sh runs a KLayout executable: DRC, LVS, the gallery and the -viewers. Without KLayout it stops with an error. With KLayout, the call +"""klayout.sh runs a KLayout executable: LVS, a DRC deck still in Ruby, +the gallery and the viewers. Without KLayout it stops with an error. With KLayout, the call goes to it unchanged. """ @@ -40,16 +40,16 @@ def stream_args(self): "/flow/util/def2stream.py", ] - def test_drc_still_needs_klayout(self): + def test_lvs_still_needs_klayout(self): result = self.run_klayout( "", - "-zz", + "-b", "-rd", "in_gds=6_final.gds", "-rd", - "report_file=6_drc.lyrdb", + "report_file=6_lvs.lvsdb", "-r", - "/flow/platforms/asap7/drc/asap7.lydrc", + "/flow/platforms/sky130hd/lvs/sky130hd.lylvs", ) self.assertEqual(result.returncode, 1) self.assertIn("Error: KLayout not found", result.stdout) diff --git a/flow/util/klayout_drc.py b/flow/util/klayout_drc.py new file mode 100644 index 0000000000..691db18ccc --- /dev/null +++ b/flow/util/klayout_drc.py @@ -0,0 +1,733 @@ +"""DRC decks in Python on the PyPI klayout module. + +KLayout's DRC language is Ruby, which the klayout Python module does not +include (KLayout/klayout#1772). The language is a thin layer over +klayout.db's Region, Edges and EdgePairs (KLayout/klayout#1980). This +module is that layer for the operations ORFS's decks use: each Layer +method calls klayout.db the way KLayout's DRC language does +(src/drc/drc/built-in-macros/_drc_layer.rb), with the same defaults, so +a deck translated line by line reports the same violations. + +Values follow the DRC language: a float is in microns (nm(20), um(1.5)), +an int is in database units; ranges are (min, max) tuples, either end +None for unbounded. +""" + +import argparse +import importlib.util +import os +import sys +import time + +try: + import klayout.db as db + import klayout.rdb as rdb +except ImportError: + db = None + rdb = None + + +def nm(v): + return v * 0.001 + + +def um(v): + return float(v) + + +def mm(v): + return v * 1000.0 + + +class _Option: + def __init__(self, kind, value): + self.kind = kind + self.value = value + + +# Check options, as the DRC language's keywords of the same names +projection = _Option("metrics", "Projection") +euclidian = _Option("metrics", "Euclidian") +square = _Option("metrics", "Square") +whole_edges = _Option("whole_edges", True) +both = _Option("both", True) + + +def _rect_filter(name): + return _Option("rect_filter", name) + + +one_side_allowed = _rect_filter("OneSideAllowed") +two_sides_allowed = _rect_filter("TwoSidesAllowed") +two_connected_sides_allowed = _rect_filter("TwoConnectedSidesAllowed") +two_opposite_sides_allowed = _rect_filter("TwoOppositeSidesAllowed") +three_sides_allowed = _rect_filter("ThreeSidesAllowed") +four_sides_allowed = _rect_filter("FourSidesAllowed") + + +def angle_limit(a): + return _Option("angle_limit", a) + + +def projection_limits(lo, hi=None): + return _Option("projection_limits", (lo, hi)) + + +transparent = _Option("shielded", False) +shielded = _Option("shielded", True) + + +class _Universal: + """A width/space/notch/separation/enclosing check inside drc(). + + Built by width(...) and compared with a value, as in the DRC + language: drc(width(projection) < nm(108)). + """ + + def __init__(self, check, other, opts): + self.check = check + self.other = other + self.opts = opts + self.lt = self.le = self.gt = self.ge = None + self.mode_or = False + + def _copy(self): + c = _Universal(self.check, self.other, self.opts) + return c + + def __lt__(self, v): + c = self._copy() + c.lt = v + return c + + def __le__(self, v): + c = self._copy() + c.le = v + return c + + def __gt__(self, v): + c = self._copy() + c.gt = v + return c + + def __ge__(self, v): + c = self._copy() + c.ge = v + return c + + def __ne__(self, v): + c = self._copy() + c.lt = v + c.gt = v + c.mode_or = True + return c + + def node(self, deck): + """CompoundRegionOperationNode, as DRCOpNodeCheck.do_create_node.""" + f = getattr(db.CompoundRegionOperationNode, "new_%s_check" % self.check) + o = deck._check_options(self.check, self.opts, universal=True) + oargs = [] + if self.other is not None: + oargs.append(db.CompoundRegionOperationNode.new_secondary(self.other.data)) + res = None + if self.lt is not None or self.le is not None: + dmin = deck.v(self.le) + 1 if self.le is not None else deck.v(self.lt) + res = f(*(oargs + [dmin] + o)) + if self.gt is not None or self.ge is not None: + dmax = deck.v(self.ge) if self.ge is not None else deck.v(self.gt) + 1 + if res is None: + res = f(*(oargs + [dmax] + o + [True])) + elif self.mode_or: + max_check = f(*(oargs + [dmax] + o + [True])) + if self.check in ("width", "notch"): + other = db.CompoundRegionOperationNode.new_edges(res) + else: + other = db.CompoundRegionOperationNode.new_edge_pair_to_first_edges( + res + ) + res_max = db.CompoundRegionOperationNode.new_edge_pair_to_first_edges( + max_check + ) + res = db.CompoundRegionOperationNode.new_join([other, res_max]) + else: + raise ValueError("min and max bounds are not supported") + return res + + +def width(*opts): + return _Universal("width", None, opts) + + +def space(*opts): + return _Universal("space", None, opts) + + +class Deck: + """The engine: the layout, the mode, and the report database.""" + + def __init__(self, in_gds, report_file, description, mode="deep", threads=1): + if db is None: + print( + "ERROR: the klayout Python module is not installed, pip install" + " klayout (etc/requirements-common_lock.txt)" + ) + sys.exit(1) + self.t0 = time.time() + self.layout = db.Layout() + print("Reading %s .." % in_gds) + self.layout.read(in_gds) + self.top = self.layout.top_cell() + self.dbu = self.layout.dbu + self.threads = threads + self.tiles = None + self.tile_borders = None + self.dss = None + self.l2n = None + if mode == "deep": + self.dss = db.DeepShapeStore() + self.dss.text_property_name = "LABEL" + self.dss.text_enlargement = 0 + self.dss.threads = threads + elif mode != "flat": + raise ValueError("mode is deep or flat, got %r" % mode) + self.report_file = report_file + self.rdb = rdb.ReportDatabase(report_file.split("/")[-1]) + self.rdb.generator = "klayout_drc.py" + self.rdb.top_cell_name = self.top.name + self.rdb.description = description + self.rdb_cell = self.rdb.create_cell(self.top.name) + self.categories = {} + + # Values, as the DRC engine's _prep_value + + def v(self, a): + if a is None: + return None + if isinstance(a, float): + return int((0.5 + a / self.dbu) // 1) + return a + + def area(self, a): + if a is None: + return None + if isinstance(a, float): + return int((0.5 + a / (self.dbu * self.dbu)) // 1) + return a + + def select(self, *sel): + """A source with a cell selection, as the DRC language's + source.select("-CELL", "+CELL", ...).""" + return Source(self, list(sel)) + + def input(self, layer, datatype=0): + return Source(self, []).input(layer, datatype) + + def polygons(self, layer, datatype=0): + return Source(self, []).polygons(layer, datatype) + + def _input(self, layer, datatype, flags, sel): + if isinstance(layer, str): + # A layer map expression, "67/0-4,6-43,45-*" + lm = db.LayerMap() + lm.map(layer, 0) + layers = [ + li + for li in self.layout.layer_indexes() + if lm.is_mapped(self.layout.get_info(li)) + ] + else: + li = self.layout.find_layer(layer, datatype) + layers = [] if li is None else [li] + if not layers and self.dss is None: + return Layer(self, db.Region()) + it = db.RecursiveShapeIterator(self.layout, self.top, layers) + it.shape_flags = flags + for c in sel: + if c == "-": + it.unselect_cells([self.top.cell_index()]) + elif c == "-*": + it.unselect_all_cells() + elif c == "+*": + it.select_all_cells() + elif c.startswith("-"): + it.unselect_cells(c[1:]) + elif c.startswith("+"): + it.select_cells(c[1:]) + else: + it.select_cells(c) + if self.dss is not None: + r = db.Region(it, self.dss, db.ICplxTrans(1.0)) + else: + r = db.Region(it, db.ICplxTrans(1.0)) + return Layer(self, r) + + def polygon_layer(self): + return Layer(self, db.Region()) + + # Connectivity, as the DRC language's netter (_drc_netter.rb) + + def _l2n_register(self, data): + if self.l2n is None: + if self.dss is not None: + self.l2n = db.LayoutToNetlist(self.dss) + else: + self.l2n = db.LayoutToNetlist(self.top.name, self.dbu) + self.l2n.name = "DRC" + self.l2n_layers = {} + if data.data_id() in self.l2n_layers: + return + name = "l%d" % (len(self.l2n_layers) + 1) + self.l2n_layers[data.data_id()] = data + self.l2n.register(data, name) + + def connect(self, a, b): + self._l2n_register(a.data) + self._l2n_register(b.data) + self.l2n.connect(a.data) + self.l2n.connect(b.data) + self.l2n.connect(a.data, b.data) + + def clear_connections(self): + self.l2n = None + + def _l2n_data(self): + if not self.l2n.is_extracted(): + self.l2n.clear_join_net_names() + self.l2n.clear_join_nets() + self.l2n.extract_netlist() + self.l2n.check_extraction_errors() + return self.l2n + + def antenna_check(self, gate, metal, ratio, *diodes): + """Gate and metal are layers (area only); diodes are layers or + (layer, area per diode) pairs.""" + dl = [] + for d in diodes: + if isinstance(d, tuple): + dl.append([d[0].data, float(d[1])]) + else: + dl.append([d.data, 0.0]) + return Layer( + self, + self._l2n_data().antenna_check( + gate.data, 1.0, 0.0, metal.data, 1.0, 0.0, ratio, dl + ), + ) + + def info(self, msg): + print("%7.1fs %s" % (time.time() - self.t0, msg)) + + def output(self, data, name, description=None): + cat = self.categories.get(name) + if cat is None: + cat = self.rdb.create_category(name) + self.categories[name] = cat + if description: + cat.description = description + cat.scan_collection(self.rdb_cell, db.CplxTrans(self.dbu), data) + + def write(self): + self.info("Writing report database: %s .." % self.report_file) + self.rdb.save(self.report_file) + + def _check_options(self, check, opts, universal=False): + """The positional arguments after the value, as _drc_layer.rb.""" + metrics = db.Region.Euclidian + minp = maxp = alim = None + whole = False + shield = None + rect_filter = 0 + for a in opts: + if isinstance(a, _Option) and a.kind == "metrics": + metrics = getattr(db.Region, a.value) + elif isinstance(a, _Option) and a.kind == "whole_edges": + whole = a.value + elif isinstance(a, _Option) and a.kind == "angle_limit": + alim = a.value + elif isinstance(a, _Option) and a.kind == "projection_limits": + lo, hi = a.value + minp = self.v(lo) + maxp = None if hi is None else self.v(hi) + elif isinstance(a, _Option) and a.kind == "shielded": + shield = a.value + elif isinstance(a, _Option) and a.kind == "rect_filter": + rect_filter |= getattr(db.Region, a.value).to_i() + else: + raise ValueError("unexpected check option %r" % (a,)) + args = [whole, metrics, alim, minp, maxp] + args.append(True if shield is None else shield) + if check not in ("width", "notch"): + args.append(db.Region.NoOppositeFilter) + args.append(db.Region.RectFilter(rect_filter)) + if not universal: + args.append(False) # negative + args.append(db.Region.IgnoreProperties) + args.append(db.Region.IncludeZeroDistanceWhenTouching) + return args + + +class Source: + """The layout's top cell, with a cell selection.""" + + def __init__(self, deck, sel): + self.deck = deck + self.sel = sel + + def select(self, *sel): + return Source(self.deck, self.sel + list(sel)) + + def input(self, layer, datatype=0): + """All shapes of a layer, texts as well.""" + return self.deck._input(layer, datatype, db.Shapes.SAll, self.sel) + + def polygons(self, layer, datatype=0): + """The polygons, boxes and paths of a layer.""" + flags = ( + db.Shapes.SBoxes + | db.Shapes.SPaths + | db.Shapes.SPolygons + | db.Shapes.SEdgePairs + ) + return self.deck._input(layer, datatype, flags, self.sel) + + +def _is_region(d): + return isinstance(d, db.Region) + + +def _is_edges(d): + return isinstance(d, db.Edges) + + +def _is_edge_pairs(d): + return isinstance(d, db.EdgePairs) + + +def _range(deck, args, conv): + """(min, max) from one value, a (min, max) tuple, or two values.""" + if len(args) == 1: + a = args[0] + if isinstance(a, tuple): + return [conv(a[0]), conv(a[1])] + return [conv(a)] + if len(args) == 2: + return [conv(args[0]), conv(args[1])] + raise ValueError("1 or 2 range arguments expected") + + +class Layer: + """A DRC layer: a Region, Edges or EdgePairs.""" + + def __init__(self, deck, data): + self.deck = deck + self.data = data + + def _new(self, data): + return Layer(self.deck, data) + + def output(self, name, description=None): + self.deck.output(self.data, name, description) + + def forget(self): + self.data = None + + # Booleans + + def __and__(self, other): + return self._new(self.data & other.data) + + def __sub__(self, other): + return self._new(self.data - other.data) + + def __or__(self, other): + return self._new(self.data | other.data) + + def __add__(self, other): + return self._new(self.data + other.data) + + def and_(self, other): + return self & other + + def not_(self, other): + return self - other + + def or_(self, other): + return self | other + + def join(self, other): + return self + other + + def andnot(self, other): + a, b = self.data.andnot(other.data) + return self._new(a), self._new(b) + + # Interactions + + def interacting(self, other, *count): + return self._new(self.data.interacting(other.data, *count)) + + def not_interacting(self, other, *count): + return self._new(self.data.not_interacting(other.data, *count)) + + def overlapping(self, other, *count): + return self._new(self.data.overlapping(other.data, *count)) + + def not_overlapping(self, other, *count): + return self._new(self.data.not_overlapping(other.data, *count)) + + def inside(self, other): + return self._new(self.data.inside(other.data)) + + def not_inside(self, other): + return self._new(self.data.not_inside(other.data)) + + def outside(self, other): + return self._new(self.data.outside(other.data)) + + def covering(self, other, *count): + return self._new(self.data.covering(other.data, *count)) + + # Checks + + def _check(self, check, *args): + value = None + other = None + rest = [] + for a in args: + if isinstance(a, Layer): + other = a + elif isinstance(a, (int, float)) and not isinstance(a, bool): + if value is not None: + raise ValueError("value already specified") + value = a + else: + rest.append(a) + if value is None: + raise ValueError("a check value must be specified") + opts = self.deck._check_options(check, rest) + if not _is_region(self.data): + # Edges checks take no shielded, filters, negative or properties + opts = opts[:5] + opts[-1:] + f = getattr(self.data, "%s_check" % check) + if other is None: + return self._new(f(self.deck.v(value), *opts)) + return self._new(f(other.data, self.deck.v(value), *opts)) + + def width(self, *args): + return self._check("width", *args) + + def space(self, *args): + return self._check("space", *args) + + def notch(self, *args): + return self._check("notch", *args) + + def isolated(self, *args): + return self._check("isolated", *args) + + def separation(self, *args): + return self._check("separation", *args) + + def enclosing(self, *args): + return self._check("enclosing", *args) + + def enclosed(self, *args): + return self._check("enclosed", *args) + + def overlap(self, *args): + return self._check("overlap", *args) + + def drc(self, op): + node = op.node(self.deck) + return self._new(self.data.complex_op(node, db.Region.IgnoreProperties)) + + def ongrid(self, gx, gy=None): + gy = gx if gy is None else gy + return self._new(self.data.grid_check(self.deck.v(gx), self.deck.v(gy))) + + # Filters + + def with_area(self, *args): + return self._new( + self.data.with_area(*_range(self.deck, args, self.deck.area), False) + ) + + def without_area(self, *args): + return self._new( + self.data.with_area(*_range(self.deck, args, self.deck.area), True) + ) + + def _bbox(self, f, inv, args): + return self._new( + getattr(self.data, "with_" + f)(*_range(self.deck, args, self.deck.v), inv) + ) + + def with_bbox_height(self, *args): + return self._bbox("bbox_height", False, args) + + def without_bbox_height(self, *args): + return self._bbox("bbox_height", True, args) + + def with_bbox_width(self, *args): + return self._bbox("bbox_width", False, args) + + def without_bbox_width(self, *args): + return self._bbox("bbox_width", True, args) + + def with_bbox_min(self, *args): + return self._bbox("bbox_min", False, args) + + def without_bbox_min(self, *args): + return self._bbox("bbox_min", True, args) + + def with_length(self, *args): + return self._length(False, args) + + def without_length(self, *args): + return self._length(True, args) + + def _length(self, inv, args): + f = "with_length" + if args and args[-1] is both: + f = "with_length_both" + args = args[:-1] + return self._new( + getattr(self.data, f)(*_range(self.deck, args, self.deck.v), inv) + ) + + def with_angle(self, *args): + return self._angle(False, args) + + def without_angle(self, *args): + return self._angle(True, args) + + def _angle(self, inv, args): + f = "with_angle" + if args and args[-1] is both: + f = "with_angle_both" + args = args[:-1] + return self._new(getattr(self.data, f)(*_range(None, args, float), inv)) + + def with_holes(self, *args): + return self._holes(False, args) + + def without_holes(self, *args): + return self._holes(True, args) + + def _holes(self, inv, args): + if not args: + lo, hi = 1, None + elif len(args) == 1 and isinstance(args[0], tuple): + lo, hi = args[0] + hi = None if hi is None else hi + 1 + elif len(args) == 1: + lo, hi = args[0], args[0] + 1 + else: + lo, hi = args[0], None if args[1] is None else args[1] + 1 + return self._new(self.data.with_holes(lo, hi, inv)) + + # Conversions + + def edges(self): + if _is_region(self.data): + return self._new(self.data.edges(db.EdgeMode.All)) + return self._new(self.data.edges()) + + def first_edges(self): + return self._new(self.data.first_edges()) + + def second_edges(self): + return self._new(self.data.second_edges()) + + def polygons(self, e=None): + if e is None: + return self._new(self.data.polygons()) + return self._new(self.data.polygons(self.deck.v(e))) + + def holes(self): + return self._new(self.data.holes()) + + def hulls(self): + return self._new(self.data.hulls()) + + def merged(self): + return self._new(self.data.merged()) + + def extents(self): + return self._new(self.data.extents()) + + def extended_out(self, d): + return self._new(self.data.extended_out(self.deck.v(d))) + + def extended_in(self, d): + return self._new(self.data.extended_in(self.deck.v(d))) + + def extended(self, b=0, e=0, o=0, i=0, joined=False): + v = self.deck.v + return self._new(self.data.extended(v(b), v(e), v(o), v(i), joined)) + + def end_segments(self, length, fraction=0.0): + return self._new(self.data.end_segments(self.deck.v(length), fraction)) + + def corners(self, amin=-180.0, amax=None): + if isinstance(amin, tuple): + amin, amax = amin + amax = amin if amax is None else amax + return self._new( + self.data.corners(float(amin), float(amax), 1, True, True, False, False) + ) + + def sized(self, dx, dy=None, mode=2): + dy = dx if dy is None else dy + return self._new(self.data.sized(self.deck.v(dx), self.deck.v(dy), mode)) + + # Queries + + def is_empty(self): + return self.data.is_empty() + + def count(self): + return self.data.count() + + +def parse_args(argv): + parser = argparse.ArgumentParser( + description="Run a Python DRC deck on a GDS/OAS file and write a" + " KLayout report database (.lyrdb)." + ) + parser.add_argument("--deck", required=True, help="the deck, a .py file") + parser.add_argument("--in-gds", required=True) + parser.add_argument("--report-file", required=True) + return parser.parse_args(argv) + + +def load_deck(path): + spec = importlib.util.spec_from_file_location("klayout_drc_deck", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def main(argv): + args = parse_args(argv) + module = load_deck(args.deck) + deck = Deck( + args.in_gds, + args.report_file, + module.DESCRIPTION, + mode=module.MODE, + threads=module.THREADS, + ) + module.drc(deck) + deck.write() + deck.info("Total violations: %d" % deck.rdb.num_items()) + return 0 + + +if __name__ == "__main__": + # The deck imports this module as klayout_drc; run that one, not + # __main__, so the options it passes are the objects checked here. + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import klayout_drc + + sys.exit(klayout_drc.main(sys.argv[1:])) From 71c426357bb450ea5f5bbd401e55f3b602519435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 08:20:22 +0100 Subject: [PATCH 03/11] nangate45: DRC deck in Python, antenna checks included MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, make drc on nangate45 needed the KLayout application to run FreePDK45.lydrc, and KLAYOUT_LVS_FILE named lvs/FreePDK45.lylvs, which does not exist. Now make drc runs FreePDK45_drc.py on the klayout Python module, and nangate45 sets no LVS deck: make lvs says LVS is not supported on this platform instead of failing on a missing file. The antenna checks go through LayoutToNetlist as the DRC language's connect and antenna_check do: the layers registered on the deep shape store, connected, the netlist extracted once and antenna_check called with the diode layer. Checked against the Ruby deck run by the KLayout 0.30.0 application on the same nangate45/gcd GDS, Python on klayout 0.30.13: clean GDS: 0 violations, both seeded GDS: 8 violations in 8 categories, both, same markers (narrow and close metal1, narrow metal2 and poly, via1 without enclosure, ACTIVE.4, a metal1 antenna) runtime: 27 s Ruby, 2.6 s Python Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- flow/platforms/nangate45/config.mk | 5 +- flow/platforms/nangate45/drc/FreePDK45.lydrc | 383 --------- flow/platforms/nangate45/drc/FreePDK45_drc.py | 746 ++++++++++++++++++ 3 files changed, 747 insertions(+), 387 deletions(-) delete mode 100644 flow/platforms/nangate45/drc/FreePDK45.lydrc create mode 100644 flow/platforms/nangate45/drc/FreePDK45_drc.py diff --git a/flow/platforms/nangate45/config.mk b/flow/platforms/nangate45/config.mk index 6371d09410..475117569f 100644 --- a/flow/platforms/nangate45/config.mk +++ b/flow/platforms/nangate45/config.mk @@ -84,10 +84,7 @@ export FASTROUTE_TCL ?= $(PLATFORM_DIR)/fastroute.tcl export KLAYOUT_TECH_FILE = $(PLATFORM_DIR)/FreePDK45.lyt # KLayout DRC ruledeck -export KLAYOUT_DRC_FILE = $(PLATFORM_DIR)/drc/FreePDK45.lydrc - -# KLayout LVS ruledeck -export KLAYOUT_LVS_FILE = $(PLATFORM_DIR)/lvs/FreePDK45.lylvs +export KLAYOUT_DRC_FILE ?= $(PLATFORM_DIR)/drc/FreePDK45_drc.py # Allow empty GDS cell export GDS_ALLOW_EMPTY ?= fakeram.* diff --git a/flow/platforms/nangate45/drc/FreePDK45.lydrc b/flow/platforms/nangate45/drc/FreePDK45.lydrc deleted file mode 100644 index 8f77c07d01..0000000000 --- a/flow/platforms/nangate45/drc/FreePDK45.lydrc +++ /dev/null @@ -1,383 +0,0 @@ - - - - - drc - - - - false - false - - true - drc_scripts - tools_menu.drc.end - dsl - drc-dsl-xml - -################################################################################ -# Rules updated from https://github.com/klayoutmatthias/FreePDK45_for_KLayout -# Reused under GNU GPLv3: https://www.gnu.org/licenses/ -# -# This DRC file for FreePDK45 was developed from the FreePDK45 DRC docs: -# https://www.eda.ncsu.edu/wiki/FreePDK45:RuleDevel -# https://www.eda.ncsu.edu/wiki/FreePDK45:Contents -################################################################################ - -#################### -# Inputs -#################### - -source($in_gds) -report("FreePDK45 DRC runset", $report_file) - -# Enable / disable rule groups -FEOL = true # Front-end-of-line checks -BEOL = true # Back-end-of-line checks -OFFGRID = true # Manufacturing grid checks -ANTENNA = true # Antenna checks - -#################### -# KLayout setup -#################### - -# Set tiling options -tiles(1000.um) -tile_borders(1.um) - -# Hierachical -deep - -# Use 4 CPU cores -threads(4) -verbose(true) - -#################### -# Layer definitions -#################### -active = polygons(1, 0) -pwell = polygons(2, 0) -nwell = polygons(3, 0) -nplus = polygons(4, 0) -pplus = polygons(5, 0) -vtg = polygons(6, 0) -vth = polygons(7, 0) -thkox = polygons(8, 0) -poly = polygons(9, 0) -cont = polygons(10, 0) -metal1 = polygons(11, 0) -via1 = polygons(12, 0) -metal2 = polygons(13, 0) -via2 = polygons(14, 0) -metal3 = polygons(15, 0) -via3 = polygons(16, 0) -metal4 = polygons(17, 0) -via4 = polygons(18, 0) -metal5 = polygons(19, 0) -via5 = polygons(20, 0) -metal6 = polygons(21, 0) -via6 = polygons(22, 0) -metal7 = polygons(23, 0) -via7 = polygons(24, 0) -metal8 = polygons(25, 0) -via8 = polygons(26, 0) -metal9 = polygons(27, 0) -via9 = polygons(28, 0) -metal10 = polygons(29, 0) - -#################### -# Helper functions -#################### - -# splits a layer classes with increasing min dimensions -def classify_by_width(layer, *dimensions) - dimensions.collect { |d| layer = layer.sized(-0.5 * (d - 1.dbu)).sized(0.5 * (d - 1.dbu)) } -end - -#################### -# Rules -#################### -# TODO(rovinski) Via enclosure rules (METALx.3, METALx.4) do not appear to correctly identify violations - -# FEOL checks -if FEOL -info("FEOL checks") - -# Well -well = nwell.or(pwell) -nwell.and(pwell).output("WELL.1", "WELL.1 : nwell/pwell must not overlap") -# the rule "WELL.2 : Minimum spacing of well at different potential : 225nm" was not coded : see : https://www.klayout.de/forum/discussion/comment/6021 -nwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of nwell at same potential : 135nm") -pwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of pwell at same potential : 135nm") -well.separation(well, 200.nm, euclidian).output("WELL.4", "WELL.4 : Minimum width of nwell/pwell : 200nm") -vtg.not(well).output("VT.1","VT.1 : Vtg adjust layers must coincide with well") -vth.not(well).output("VT.1","VT.1 : Vth adjust layers must coincide with well") - -# Poly -gate = poly & active -poly.width(50.nm, euclidian).output("POLY.1", "POLY.1 : Minimum width of poly : 50nm") - -# TODO(rovinski) This section errors out for some reason -##if poly.separation(active, 140.nm, projection).polygons? -# poly.separation(active, 140.nm, projection).polygons.without_area(0).output("POLY.2", "POLY.2 : Minimum spacing of poly AND active: 140nm") -##end -#poly.enclosing(gate, 55.nm, projection).polygons.without_area(0).output("POLY.3", "POLY.3 : Minimum poly extension beyond active : 55nm") - -active.enclosing(gate, 70.nm, projection).polygons.without_area(0).output("POLY.4", "POLY.4 : Minimum enclosure of active around gate : 70nm") -poly.not(active).separation(active, 50.nm, projection).polygons.without_area(0).output("POLY.5", "POLY.5 : Minimum spacing of field poly to active: 50nm") -poly.space(75.nm, euclidian).output("POLY.6", "POLY.6 : Minimum spacing of field poly: 75nm") - -# Active -active.width(90.nm, euclidian).output("ACTIVE.1", "ACTIVE.1 : Minimum width of active : 90nm") -active.space(80.nm, euclidian).output("ACTIVE.2", "ACTIVE.2 : Minimum spacing of active : 80nm") -well.enclosing(active, 55.nm, euclidian).output("ACTIVE.3", "ACTIVE.3 : Minimum enclosure/spacing of nwell/pwell to active: 55nm") -active.not(well).output("ACTIVE.4", "ACTIVE.4 : active must be inside nwell or pwell") - -# Implant -implant = nplus.or(pplus) -implant.separation(gate, 70.nm, projection).polygons.without_area(0).output("IMPLANT.1", "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to channel : 70nm") -implant.separation(cont, 25.nm, projection).polygons.without_area(0).output("IMPLANT.2", "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to contact : 25nm") -implant.width(45.nm, euclidian).output("IMPLANT.3", "IMPLANT.3 : Minimum width of nimplant/ pimplant : 45nm") -implant.space(45.nm, euclidian).output("IMPLANT.4", "IMPLANT.4 : Minimum spacing of nimplant/ pimplant : 45nm") -nplus.and(pplus).output("IMPLANT.5", "IMPLANT.5 : Nimplant and pimplant must not overlap") - -# Contact -cont.width(65.nm).output("CONTACT.1", "CONTACT.1 : Minimum/Maximum width of contact : 65nm") -cont.space(75.nm, euclidian).output("CONTACT.2", "CONTACT.2 : Minimum spacing of contact : 75nm") -cont.not(active.or(poly.or(metal1))).output("CONTACT.3", "CONTACT.3 : contact must be inside active or poly or metal1") -active.enclosing(cont, 5.nm, euclidian).output("CONTACT.4", "CONTACT.4 : Minimum enclosure of active around contact : 5nm") -poly.enclosing(cont, 5.nm, euclidian).output("CONTACT.5", "CONTACT.5 : Minimum enclosure of poly around contact : 5nm") -cont.separation(poly, 35.nm, euclidian).output("CONTACT.6", "CONTACT.6 : Minimum spacing of contact and poly : 35nm") - -end # FEOL - -# BEOL checks -if BEOL -info("BEOL checks") - -# Metal1 -metal1.width(65.nm, euclidian).output("METAL1.1", "METAL1.1 : Minimum width of metal1 : 65nm") -metal1.space(65.nm, euclidian).output("METAL1.2", "METAL1.2 : Minimum spacing of metal1 : 65nm") -cont_edges_with_less_enclosure = metal1.enclosing(cont, 35.nm, projection).second_edges -error_corners = cont_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) -cont.interacting(error_corners.polygons(1.dbu)).output("METAL1.3", "METAL1.3 : Minimum enclosure around contact on two opposite sides : 35nm") -via1_edges_with_less_enclosure = metal1.enclosing(via1, 35.nm, projection).second_edges -error_corners = via1_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) -via1.interacting(error_corners.polygons(1.dbu)).output("METAL1.4", "METAL1.4 : Minimum enclosure around via1 on two opposite sides : 35nm") -metal1_gt90, metal1_gt270, metal1_gt500, metal1_gt900, metal1_gt1500 = classify_by_width(metal1, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm) -metal1_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL1.5", "METAL1.5 : Minimum spacing of metal1 wider than 90 nm and longer than 300 nm : 90nm") -metal1_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL1.6", "METAL1.6 : Minimum spacing of metal1 wider than 270 nm and longer than 900 nm : 270nm") -metal1_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL1.7", "METAL1.7 : Minimum spacing of metal1 wider than 500 nm and longer than 1.8 um : 500nm") -metal1_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL1.8", "METAL1.8 : Minimum spacing of metal1 wider than 900 nm and longer than 2.7 um : 900nm") -metal1_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL1.9", "METAL1.9 : Minimum spacing of metal1 wider than 1500 nm and longer than 4.0 um : 1500nm") - -# via1 -via1.width(65.nm, euclidian).output("VIA1.1", "VIA1.1 : Minimum/Maximum width of via1 : 65nm") -via1.space(75.nm, euclidian).output("VIA1.2", "VIA1.2 : Minimum spacing of via1 : 75nm") -via1.not(metal1).output("VIA1.3", "VIA1.3 : via1 must be inside metal1") -via1.not(metal2).output("VIA1.4", "VIA1.4 : via1 must be inside metal2") - -# metal2 -metal2.width(70.nm, euclidian).output("METAL2.1", "METAL2.1 : Minimum width of intermediate metal2 : 70nm") -metal2.space(70.nm, euclidian).output("METAL2.2", "METAL2.2 : Minimum spacing of intermediate metal2 : 70nm") -via1_edges_with_less_enclosure = metal2.enclosing(via1, 35.nm, projection).second_edges -error_corners = via1_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) -via1.interacting(error_corners.polygons(1.dbu)).output("METAL2.3", "METAL2.3 : Minimum enclosure around via1 on two opposite sides : 35nm") -via2_edges_with_less_enclosure = metal2.enclosing(via2, 35.nm, projection).second_edges -error_corners = via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) -via2.interacting(error_corners.polygons(1.dbu)).output("METAL2.4", "METAL2.4 : Minimum enclosure around via2 on two opposite sides : 35nm") -metal2_gt90, metal2_gt270, metal2_gt500, metal2_gt900, metal2_gt1500 = classify_by_width(metal2, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm) -metal2_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL2.5", "METAL2.5 : Minimum spacing of intermediate metal2 wider than 90 nm and longer than 300 nm : 90nm") -metal2_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL2.6", "METAL2.6 : Minimum spacing of intermediate metal2 wider than 270 nm and longer than 900 nm : 270nm") -metal2_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL2.7", "METAL2.7 : Minimum spacing of intermediate metal2 wider than 500 nm and longer than 1.8 um : 500nm") -metal2_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL2.8", "METAL2.8 : Minimum spacing of intermediate metal2 wider than 900 nm and longer than 2.7 um : 900nm") -metal2_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL2.9", "METAL2.9 : Minimum spacing of intermediate metal2 wider than 1500 nm and longer than 4.0 um : 1500nm") - -# via2 -via2.width(70.nm, euclidian).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 70nm") -via2.space(85.nm, euclidian).output("VIA2.2", "VIA2.2 : Minimum spacing of via2 : 85nm") -via2.not(metal2).output("VIA2.3", "VIA2.3 : via2 must be inside metal2") -via2.not(metal3).output("VIA2.4", "VIA2.4 : via2 must be inside metal3") - -# metal3 -metal3.width(70.nm, euclidian).output("METAL3.1", "METAL3.1 : Minimum width of intermediate metal3 : 70nm") -metal3.space(70.nm, euclidian).output("METAL3.2", "METAL3.2 : Minimum spacing of intermediate metal3 : 70nm") -via2_edges_with_less_enclosure = metal3.enclosing(via2, 35.nm, projection).second_edges -error_corners = via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) -via2.interacting(error_corners.polygons(1.dbu)).output("METAL3.3", "METAL3.3 : Minimum enclosure around via2 on two opposite sides : 35nm") -via3_edges_with_less_enclosure = metal3.enclosing(via3, 35.nm, projection).second_edges -error_corners = via3_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) -via3.interacting(error_corners.polygons(1.dbu)).output("METAL3.4", "METAL3.4 : Minimum enclosure around via3 on two opposite sides : 35nm") -metal3_gt90, metal3_gt270, metal3_gt500, metal3_gt900, metal3_gt1500 = classify_by_width(metal3, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm) -metal3_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL3.5", "METAL3.5 : Minimum spacing of intermediate metal3 wider than 90 nm and longer than 300 nm : 90nm") -metal3_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL3.6", "METAL3.6 : Minimum spacing of intermediate metal3 wider than 270 nm and longer than 900 nm : 270nm") -metal3_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL3.7", "METAL3.7 : Minimum spacing of intermediate metal3 wider than 500 nm and longer than 1.8 um : 500nm") -metal3_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL3.8", "METAL3.8 : Minimum spacing of intermediate metal3 wider than 900 nm and longer than 2.7 um : 900nm") -metal3_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL3.9", "METAL3.9 : Minimum spacing of intermediate metal3 wider than 1500 nm and longer than 4.0 um : 1500nm") - -# via3 -via3.width(70.nm, euclidian).output("VIA3.1", "VIA3.1 : Minimum/Maximum width of via3 : 70nm") -via3.space(85.nm, euclidian).output("VIA3.2", "VIA3.2 : Minimum spacing of via3 : 85nm") -via3.not(metal3).output("VIA3.3", "VIA3.3 : via3 must be inside metal3") -via3.not(metal4).output("VIA3.4", "VIA3.4 : via3 must be inside metal4") - -# metal4 -metal4.width(140.nm, euclidian).output("METAL4.1", "METAL4.1 : Minimum width of semi-global metal4 : 140nm") -metal4.space(140.nm, euclidian).output("METAL4.2", "METAL4.2 : Minimum spacing of semi-global metal4 : 140nm") -metal4_gt270, metal4_gt500, metal4_gt900 = classify_by_width(metal4, 270.nm, 500.nm, 900.nm) -metal4_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL4.6", "METAL4.6 : Minimum spacing of semi-global metal4 wider than 270 nm and longer than 900 nm : 270nm") -metal4_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL4.7", "METAL4.7 : Minimum spacing of semi-global metal4 wider than 500 nm and longer than 1.8 um : 500nm") -metal4_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL4.8", "METAL4.8 : Minimum spacing of semi-global meta4l wider than 900 nm and longer than 2.7 um : 900nm") - -# via4 -via4.width(140.nm, euclidian).output("VIA4.1", "VIA4.1 : Minimum width of via4 : 140nm") -via4.space(160.nm, euclidian).output("VIA4.2", "VIA4.2 : Minimum spacing of via4 : 160nm") -via4.not(metal4).output("VIA4.3", "VIA4.3 : via4 must be inside metal4") -via4.not(metal5).output("VIA4.4", "VIA4.4 : via4 must be inside metal5") - -# metal5 -metal5.width(140.nm, euclidian).output("METAL5.1", "METAL5.1 : Minimum width of semi-global metal5 : 140nm") -metal5.space(140.nm, euclidian).output("METAL5.2", "METAL5.2 : Minimum spacing of semi-global metal5 : 140nm") -metal5_gt270, metal5_gt500, metal5_gt900 = classify_by_width(metal5, 270.nm, 500.nm, 900.nm) -metal5_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL5.6", "METAL5.6 : Minimum spacing of semi-global metal5 wider than 270 nm and longer than 900 nm : 270nm") -metal5_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL5.7", "METAL5.7 : Minimum spacing of semi-global metal5 wider than 500 nm and longer than 1.8 um : 500nm") -metal5_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL5.8", "METAL5.8 : Minimum spacing of semi-global meta5l wider than 900 nm and longer than 2.7 um : 900nm") - -# via5 -via5.width(140.nm, euclidian).output("VIA5.1", "VIA5.1 : Minimum/Maximum width of via5 : 140nm") -via5.space(160.nm, euclidian).output("VIA5.2", "VIA5.2 : Minimum spacing of via5 : 160nm") -via5.not(metal5).output("VIA5.3", "VIA5.3 : via5 must be inside metal5") -via5.not(metal6).output("VIA5.4", "VIA5.4 : via5 must be inside metal6") - -# metal6 -metal6.width(140.nm, euclidian).output("METAL6.1", "METAL6.1 : Minimum width of semi-global metal6 : 140nm") -metal6.space(140.nm, euclidian).output("METAL6.2", "METAL6.2 : Minimum spacing of semi-global metal6 : 140nm") -metal6_gt270, metal6_gt500, metal6_gt900 = classify_by_width(metal6, 270.nm, 500.nm, 900.nm) -metal6_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL6.6", "METAL6.6 : Minimum spacing of semi-global metal6 wider than 270 nm and longer than 900 nm : 270nm") -metal6_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL6.7", "METAL6.7 : Minimum spacing of semi-global metal6 wider than 500 nm and longer than 1.8 um : 500nm") -metal6_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL6.8", "METAL6.8 : Minimum spacing of semi-global metal6 wider than 900 nm and longer than 2.7 um : 900nm") - -# via6 -via6.width(140.nm, euclidian).output("VIA6.1", "VIA6.1 : Minimum/Maximum width of via6 : 140nm") -via6.space(160.nm, euclidian).output("VIA6.2", "VIA6.2 : Minimum spacing of via6 : 160nm") -via6.not(metal6).output("VIA6.3", "VIA6.3 : via6 must be inside metal6") -via6.not(metal7).output("VIA6.4", "VIA6.4 : via6 must be inside metal7") - -# metal7 -metal7.width(400.nm, euclidian).output("METAL7.1", "METAL7.1 : Minimum width of thin global metal7 : 400nm") -metal7.space(400.nm, euclidian).output("METAL7.2", "METAL7.2 : Minimum spacing of thin global metal7 : 400nm") -metal7_gt500, metal7_gt900, metal7_gt1500 = classify_by_width(metal7, 500.nm, 900.nm, 1500.nm) -metal7_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL7.7", "METAL7.7 : Minimum spacing of thin global metal7 wider than 500 nm and longer than 1.8 um : 500nm") -metal7_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL7.8", "METAL7.8 : Minimum spacing of thin global metal7 wider than 900 nm and longer than 2.7 um : 900nm") -metal7_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL7.9", "METAL7.9 : Minimum spacing of thin global meta7l wider than 1500 nm and longer than 4.0 um : 1500nm") - -# via7 -via7.width(400.nm, euclidian).output("VIA6.1", "VIA6.1 : Minimum/Maximum width of via7 : 400nm") -via7.space(440.nm, euclidian).output("VIA6.2", "VIA6.2 : Minimum spacing of via7 : 440nm") -via7.not(metal7).output("VIA7.3", "VIA7.3 : via7 must be inside metal7") -via7.not(metal8).output("VIA7.4", "VIA7.4 : via7 must be inside metal8") - -# metal8 -metal8.width(400.nm, euclidian).output("METAL8.1", "METAL8.1 : Minimum width of thin global metal8 : 400nm") -metal8.space(400.nm, euclidian).output("METAL8.2", "METAL8.2 : Minimum spacing of thin global metal8 : 400nm") -metal8_gt500, metal8_gt900, metal8_gt1500 = classify_by_width(metal8, 500.nm, 900.nm, 1500.nm) -metal8_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL8.7", "METAL8.7 : Minimum spacing of thin global metal8 wider than 500 nm and longer than 1.8 um : 500nm") -metal8_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL8.8", "METAL8.8 : Minimum spacing of thin global metal8 wider than 900 nm and longer than 2.7 um : 900nm") -metal8_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL8.9", "METAL8.9 : Minimum spacing of thin global metal8 wider than 1500 nm and longer than 4.0 um : 1500nm") - -# via8 -via8.width(400.nm, euclidian).output("VIA8.1", "VIA8.1 : Minimum/Maximum width of via8 : 400nm") -via8.space(440.nm, euclidian).output("VIA8.2", "VIA8.2 : Minimum spacing of via8 : 440nm") -via8.not(metal8).output("VIA8.3", "VIA8.3 : via8 must be inside metal8") -via8.not(metal9).output("VIA8.4", "VIA8.4 : via8 must be inside metal9") - -# metal9 -metal9.width(800.nm, euclidian).output("METAL9.1", "METAL9.1 : Minimum width of global metal9 : 800nm") -metal9.space(800.nm, euclidian).output("METAL9.2", "METAL9.2 : Minimum spacing of global metal9 : 800nm") -metal9_gt500, metal9_gt900, metal9_gt1500 = classify_by_width(metal9, 500.nm, 900.nm, 1500.nm) -metal9_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL9.7", "METAL9.7 : Minimum spacing of global metal9 wider than 500 nm and longer than 1.8 um : 500nm") -metal9_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL9.8", "METAL9.8 : Minimum spacing of global metal9 wider than 900 nm and longer than 2.7 um : 900nm") -metal9_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL9.9", "METAL9.9 : Minimum spacing of global metal9 wider than 1500 nm and longer than 4.0 um : 1500nm") - -# via9 -via9.width(800.nm, euclidian).output("VIA9.1", "VIA9.1 : Minimum/Maximum width of via9 : 800nm") -via9.space(880.nm, euclidian).output("VIA9.2", "VIA9.2 : Minimum spacing of via9 : 880nm") -via9.not(metal9).output("VIA9.3", "VIA9.3 : via9 must be inside metal9") -via9.not(metal10).output("VIA9.4", "VIA9.4 : via9 must be inside metal10") - -# metal10 -metal10.width(800.nm, euclidian).output("METAL10.1", "METAL10.1 : Minimum width of global metal10 : 800nm") -metal10.space(800.nm, euclidian).output("METAL10.2", "METAL10.2 : Minimum spacing of global metal10 : 800nm") -metal10_gt500, metal10_gt900, metal10_gt1500 = classify_by_width(metal10, 500.nm, 900.nm, 1500.nm) -metal10_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL10.7", "METAL10.7 : Minimum spacing of global metal10 wider than 500 nm and longer than 1.8 um : 500nm") -metal10_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL10.8", "METAL10.8 : Minimum spacing of global metal10 wider than 900 nm and longer than 2.7 um : 900nm") -metal10_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL10.9", "METAL10.9 : Minimum spacing of global metal10 wider than 1500 nm and longer than 4.0 um : 1500nm") - -end # BEOL - -# Manufacturing grid check -# https://www.eda.ncsu.edu/wiki/FreePDK45:Manufacturing_Grid - -if OFFGRID -info("Off-grid checks") - -grid = 2.5.nm -all_drawing = [ :well, :active, :vtg, :vth, :pplus, :nplus, :poly, :thkox, :cont, :metal1, :via1, :metal2, :via2, :metal3, :via3, :metal4, :via4, :metal5, :via5, :metal6, :via6, :metal7, :via7, :metal8, :via8, :metal9, :via9, :metal10 ] - -# A Ruby idiom to get the value of a variable whose name is in "dwg" (as symbol) -# TODO(rovinski) local_variable_get is not compatible with older Ruby versions -#all_drawing.each do |dwg| -# layer = binding.local_variable_get(dwg) -# r_grid = layer.ongrid(grid).polygons(10.nm) -# r_grid.output("GRID: vertexes on layer #{dwg} not on grid of #{'%.12g' % grid}") -#end -end # OFFGRID - -# Antenna checks - -if ANTENNA -info("ANTENNA checks") - -# Create layer connections -connect(gate, poly) -connect(poly, cont) -connect(cont, metal1) -connect(metal1, via1) -connect(via1, metal2) -connect(metal2, via2) -connect(via2, metal3) -connect(metal3, via3) -connect(via3, metal4) -connect(metal4, via4) -connect(via4, metal5) -connect(metal5, via5) -connect(via5, metal6) -connect(metal6, via6) -connect(via6, metal7) -connect(metal7, via7) -connect(via7, metal8) -connect(metal8, via8) -connect(via8, metal9) -connect(metal9, via9) -connect(via9, metal10) - -# Create and connect diodes -diode = nplus & active - nwell -connect(diode, cont) - -antenna_check(gate, metal1, 300.0, diode).output("METAL1_ANTENNA", "METAL1_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") -antenna_check(gate, metal2, 300.0, diode).output("METAL2_ANTENNA", "METAL2_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") -antenna_check(gate, metal3, 300.0, diode).output("METAL3_ANTENNA", "METAL3_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") -antenna_check(gate, metal4, 300.0, diode).output("METAL4_ANTENNA", "METAL4_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") -antenna_check(gate, metal5, 300.0, diode).output("METAL5_ANTENNA", "METAL5_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") -antenna_check(gate, metal6, 300.0, diode).output("METAL6_ANTENNA", "METAL6_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") -antenna_check(gate, metal7, 300.0, diode).output("METAL7_ANTENNA", "METAL7_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") -antenna_check(gate, metal8, 300.0, diode).output("METAL8_ANTENNA", "METAL8_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") -antenna_check(gate, metal9, 300.0, diode).output("METAL9_ANTENNA", "METAL9_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") -antenna_check(gate, metal10, 300.0, diode).output("METAL10_ANTENNA", "METAL10_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") - -# Remove connections -clear_connections -end # ANTENNA - - diff --git a/flow/platforms/nangate45/drc/FreePDK45_drc.py b/flow/platforms/nangate45/drc/FreePDK45_drc.py new file mode 100644 index 0000000000..783a06bdaa --- /dev/null +++ b/flow/platforms/nangate45/drc/FreePDK45_drc.py @@ -0,0 +1,746 @@ +################################################################################ +# Rules updated from https://github.com/klayoutmatthias/FreePDK45_for_KLayout +# Reused under GNU GPLv3: https://www.gnu.org/licenses/ +# +# This DRC file for FreePDK45 was developed from the FreePDK45 DRC docs: +# https://www.eda.ncsu.edu/wiki/FreePDK45:RuleDevel +# https://www.eda.ncsu.edu/wiki/FreePDK45:Contents +"""FreePDK45 DRC deck, run by flow/util/klayout_drc.py on the klayout module. + +Translated line by line from KLayout's DRC language. +""" + +from klayout_drc import angle_limit, euclidian, nm, projection, um + +DESCRIPTION = "FreePDK45 DRC runset" +MODE = "deep" +THREADS = 4 + +FEOL = True # Front-end-of-line checks +BEOL = True # Back-end-of-line checks +OFFGRID = True # Manufacturing grid checks +ANTENNA = True # Antenna checks + + +# splits a layer classes with increasing min dimensions +def classify_by_width(deck, layer, *dimensions): + result = [] + for d in dimensions: + layer = layer.sized(-0.5 * (d - deck.dbu)).sized(0.5 * (d - deck.dbu)) + result.append(layer) + return result + + +def drc(deck): + #################### + # Layer definitions + #################### + active = deck.polygons(1, 0) + pwell = deck.polygons(2, 0) + nwell = deck.polygons(3, 0) + nplus = deck.polygons(4, 0) + pplus = deck.polygons(5, 0) + vtg = deck.polygons(6, 0) + vth = deck.polygons(7, 0) + thkox = deck.polygons(8, 0) + poly = deck.polygons(9, 0) + cont = deck.polygons(10, 0) + metal1 = deck.polygons(11, 0) + via1 = deck.polygons(12, 0) + metal2 = deck.polygons(13, 0) + via2 = deck.polygons(14, 0) + metal3 = deck.polygons(15, 0) + via3 = deck.polygons(16, 0) + metal4 = deck.polygons(17, 0) + via4 = deck.polygons(18, 0) + metal5 = deck.polygons(19, 0) + via5 = deck.polygons(20, 0) + metal6 = deck.polygons(21, 0) + via6 = deck.polygons(22, 0) + metal7 = deck.polygons(23, 0) + via7 = deck.polygons(24, 0) + metal8 = deck.polygons(25, 0) + via8 = deck.polygons(26, 0) + metal9 = deck.polygons(27, 0) + via9 = deck.polygons(28, 0) + metal10 = deck.polygons(29, 0) + + #################### + # Helper functions + #################### + + # splits a layer classes with increasing min dimensions + + #################### + # Rules + #################### + # TODO(rovinski) Via enclosure rules (METALx.3, METALx.4) do not appear to correctly identify violations + + # FEOL checks + if FEOL: + deck.info("FEOL checks") + + # Well + well = nwell.or_(pwell) + nwell.and_(pwell).output("WELL.1", "WELL.1 : nwell/pwell must not overlap") + # the rule "WELL.2 : Minimum spacing of well at different potential : 225nm" was not coded : see : https://www.klayout.de/forum/discussion/comment/6021 + nwell.space(nm(135), euclidian).output( + "WELL.3", "WELL.3 : Minimum spacing of nwell at same potential : 135nm" + ) + pwell.space(nm(135), euclidian).output( + "WELL.3", "WELL.3 : Minimum spacing of pwell at same potential : 135nm" + ) + well.separation(well, nm(200), euclidian).output( + "WELL.4", "WELL.4 : Minimum width of nwell/pwell : 200nm" + ) + vtg.not_(well).output( + "VT.1", "VT.1 : Vtg adjust layers must coincide with well" + ) + vth.not_(well).output( + "VT.1", "VT.1 : Vth adjust layers must coincide with well" + ) + + # Poly + gate = poly & active + poly.width(nm(50), euclidian).output( + "POLY.1", "POLY.1 : Minimum width of poly : 50nm" + ) + + # TODO(rovinski) This section errors out for some reason + ##if poly.separation(active, 140.nm, projection).polygons? + # poly.separation(active, 140.nm, projection).polygons.without_area(0).output("POLY.2", "POLY.2 : Minimum spacing of poly AND active: 140nm") + ##end + # poly.enclosing(gate, 55.nm, projection).polygons.without_area(0).output("POLY.3", "POLY.3 : Minimum poly extension beyond active : 55nm") + + active.enclosing(gate, nm(70), projection).polygons().without_area(0).output( + "POLY.4", "POLY.4 : Minimum enclosure of active around gate : 70nm" + ) + poly.not_(active).separation( + active, nm(50), projection + ).polygons().without_area(0).output( + "POLY.5", "POLY.5 : Minimum spacing of field poly to active: 50nm" + ) + poly.space(nm(75), euclidian).output( + "POLY.6", "POLY.6 : Minimum spacing of field poly: 75nm" + ) + + # Active + active.width(nm(90), euclidian).output( + "ACTIVE.1", "ACTIVE.1 : Minimum width of active : 90nm" + ) + active.space(nm(80), euclidian).output( + "ACTIVE.2", "ACTIVE.2 : Minimum spacing of active : 80nm" + ) + well.enclosing(active, nm(55), euclidian).output( + "ACTIVE.3", + "ACTIVE.3 : Minimum enclosure/spacing of nwell/pwell to active: 55nm", + ) + active.not_(well).output( + "ACTIVE.4", "ACTIVE.4 : active must be inside nwell or pwell" + ) + + # Implant + implant = nplus.or_(pplus) + implant.separation(gate, nm(70), projection).polygons().without_area(0).output( + "IMPLANT.1", + "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to channel : 70nm", + ) + implant.separation(cont, nm(25), projection).polygons().without_area(0).output( + "IMPLANT.2", + "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to contact : 25nm", + ) + implant.width(nm(45), euclidian).output( + "IMPLANT.3", "IMPLANT.3 : Minimum width of nimplant/ pimplant : 45nm" + ) + implant.space(nm(45), euclidian).output( + "IMPLANT.4", "IMPLANT.4 : Minimum spacing of nimplant/ pimplant : 45nm" + ) + nplus.and_(pplus).output( + "IMPLANT.5", "IMPLANT.5 : Nimplant and pimplant must not overlap" + ) + + # Contact + cont.width(nm(65)).output( + "CONTACT.1", "CONTACT.1 : Minimum/Maximum width of contact : 65nm" + ) + cont.space(nm(75), euclidian).output( + "CONTACT.2", "CONTACT.2 : Minimum spacing of contact : 75nm" + ) + cont.not_(active.or_(poly.or_(metal1))).output( + "CONTACT.3", "CONTACT.3 : contact must be inside active or poly or metal1" + ) + active.enclosing(cont, nm(5), euclidian).output( + "CONTACT.4", "CONTACT.4 : Minimum enclosure of active around contact : 5nm" + ) + poly.enclosing(cont, nm(5), euclidian).output( + "CONTACT.5", "CONTACT.5 : Minimum enclosure of poly around contact : 5nm" + ) + cont.separation(poly, nm(35), euclidian).output( + "CONTACT.6", "CONTACT.6 : Minimum spacing of contact and poly : 35nm" + ) + + # BEOL checks + if BEOL: + deck.info("BEOL checks") + + # Metal1 + metal1.width(nm(65), euclidian).output( + "METAL1.1", "METAL1.1 : Minimum width of metal1 : 65nm" + ) + metal1.space(nm(65), euclidian).output( + "METAL1.2", "METAL1.2 : Minimum spacing of metal1 : 65nm" + ) + cont_edges_with_less_enclosure = metal1.enclosing( + cont, nm(35), projection + ).second_edges() + error_corners = cont_edges_with_less_enclosure.width( + angle_limit(100.0), 1 * deck.dbu + ) + cont.interacting(error_corners.polygons(1 * deck.dbu)).output( + "METAL1.3", + "METAL1.3 : Minimum enclosure around contact on two opposite sides : 35nm", + ) + via1_edges_with_less_enclosure = metal1.enclosing( + via1, nm(35), projection + ).second_edges() + error_corners = via1_edges_with_less_enclosure.width( + angle_limit(100.0), 1 * deck.dbu + ) + via1.interacting(error_corners.polygons(1 * deck.dbu)).output( + "METAL1.4", + "METAL1.4 : Minimum enclosure around via1 on two opposite sides : 35nm", + ) + metal1_gt90, metal1_gt270, metal1_gt500, metal1_gt900, metal1_gt1500 = ( + classify_by_width(deck, metal1, nm(90), nm(270), nm(500), nm(900), nm(1500)) + ) + metal1_gt90.edges().with_length(nm(300), None).space(nm(90), euclidian).output( + "METAL1.5", + "METAL1.5 : Minimum spacing of metal1 wider than 90 nm and longer than 300 nm : 90nm", + ) + metal1_gt270.edges().with_length(nm(900), None).space( + nm(270), euclidian + ).output( + "METAL1.6", + "METAL1.6 : Minimum spacing of metal1 wider than 270 nm and longer than 900 nm : 270nm", + ) + metal1_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL1.7", + "METAL1.7 : Minimum spacing of metal1 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal1_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL1.8", + "METAL1.8 : Minimum spacing of metal1 wider than 900 nm and longer than 2.7 um : 900nm", + ) + metal1_gt1500.edges().with_length(um(4), None).space( + nm(1500), euclidian + ).output( + "METAL1.9", + "METAL1.9 : Minimum spacing of metal1 wider than 1500 nm and longer than 4.0 um : 1500nm", + ) + + # via1 + via1.width(nm(65), euclidian).output( + "VIA1.1", "VIA1.1 : Minimum/Maximum width of via1 : 65nm" + ) + via1.space(nm(75), euclidian).output( + "VIA1.2", "VIA1.2 : Minimum spacing of via1 : 75nm" + ) + via1.not_(metal1).output("VIA1.3", "VIA1.3 : via1 must be inside metal1") + via1.not_(metal2).output("VIA1.4", "VIA1.4 : via1 must be inside metal2") + + # metal2 + metal2.width(nm(70), euclidian).output( + "METAL2.1", "METAL2.1 : Minimum width of intermediate metal2 : 70nm" + ) + metal2.space(nm(70), euclidian).output( + "METAL2.2", "METAL2.2 : Minimum spacing of intermediate metal2 : 70nm" + ) + via1_edges_with_less_enclosure = metal2.enclosing( + via1, nm(35), projection + ).second_edges() + error_corners = via1_edges_with_less_enclosure.width( + angle_limit(100.0), 1 * deck.dbu + ) + via1.interacting(error_corners.polygons(1 * deck.dbu)).output( + "METAL2.3", + "METAL2.3 : Minimum enclosure around via1 on two opposite sides : 35nm", + ) + via2_edges_with_less_enclosure = metal2.enclosing( + via2, nm(35), projection + ).second_edges() + error_corners = via2_edges_with_less_enclosure.width( + angle_limit(100.0), 1 * deck.dbu + ) + via2.interacting(error_corners.polygons(1 * deck.dbu)).output( + "METAL2.4", + "METAL2.4 : Minimum enclosure around via2 on two opposite sides : 35nm", + ) + metal2_gt90, metal2_gt270, metal2_gt500, metal2_gt900, metal2_gt1500 = ( + classify_by_width(deck, metal2, nm(90), nm(270), nm(500), nm(900), nm(1500)) + ) + metal2_gt90.edges().with_length(nm(300), None).space(nm(90), euclidian).output( + "METAL2.5", + "METAL2.5 : Minimum spacing of intermediate metal2 wider than 90 nm and longer than 300 nm : 90nm", + ) + metal2_gt270.edges().with_length(nm(900), None).space( + nm(270), euclidian + ).output( + "METAL2.6", + "METAL2.6 : Minimum spacing of intermediate metal2 wider than 270 nm and longer than 900 nm : 270nm", + ) + metal2_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL2.7", + "METAL2.7 : Minimum spacing of intermediate metal2 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal2_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL2.8", + "METAL2.8 : Minimum spacing of intermediate metal2 wider than 900 nm and longer than 2.7 um : 900nm", + ) + metal2_gt1500.edges().with_length(um(4), None).space( + nm(1500), euclidian + ).output( + "METAL2.9", + "METAL2.9 : Minimum spacing of intermediate metal2 wider than 1500 nm and longer than 4.0 um : 1500nm", + ) + + # via2 + via2.width(nm(70), euclidian).output( + "VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 70nm" + ) + via2.space(nm(85), euclidian).output( + "VIA2.2", "VIA2.2 : Minimum spacing of via2 : 85nm" + ) + via2.not_(metal2).output("VIA2.3", "VIA2.3 : via2 must be inside metal2") + via2.not_(metal3).output("VIA2.4", "VIA2.4 : via2 must be inside metal3") + + # metal3 + metal3.width(nm(70), euclidian).output( + "METAL3.1", "METAL3.1 : Minimum width of intermediate metal3 : 70nm" + ) + metal3.space(nm(70), euclidian).output( + "METAL3.2", "METAL3.2 : Minimum spacing of intermediate metal3 : 70nm" + ) + via2_edges_with_less_enclosure = metal3.enclosing( + via2, nm(35), projection + ).second_edges() + error_corners = via2_edges_with_less_enclosure.width( + angle_limit(100.0), 1 * deck.dbu + ) + via2.interacting(error_corners.polygons(1 * deck.dbu)).output( + "METAL3.3", + "METAL3.3 : Minimum enclosure around via2 on two opposite sides : 35nm", + ) + via3_edges_with_less_enclosure = metal3.enclosing( + via3, nm(35), projection + ).second_edges() + error_corners = via3_edges_with_less_enclosure.width( + angle_limit(100.0), 1 * deck.dbu + ) + via3.interacting(error_corners.polygons(1 * deck.dbu)).output( + "METAL3.4", + "METAL3.4 : Minimum enclosure around via3 on two opposite sides : 35nm", + ) + metal3_gt90, metal3_gt270, metal3_gt500, metal3_gt900, metal3_gt1500 = ( + classify_by_width(deck, metal3, nm(90), nm(270), nm(500), nm(900), nm(1500)) + ) + metal3_gt90.edges().with_length(nm(300), None).space(nm(90), euclidian).output( + "METAL3.5", + "METAL3.5 : Minimum spacing of intermediate metal3 wider than 90 nm and longer than 300 nm : 90nm", + ) + metal3_gt270.edges().with_length(nm(900), None).space( + nm(270), euclidian + ).output( + "METAL3.6", + "METAL3.6 : Minimum spacing of intermediate metal3 wider than 270 nm and longer than 900 nm : 270nm", + ) + metal3_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL3.7", + "METAL3.7 : Minimum spacing of intermediate metal3 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal3_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL3.8", + "METAL3.8 : Minimum spacing of intermediate metal3 wider than 900 nm and longer than 2.7 um : 900nm", + ) + metal3_gt1500.edges().with_length(um(4), None).space( + nm(1500), euclidian + ).output( + "METAL3.9", + "METAL3.9 : Minimum spacing of intermediate metal3 wider than 1500 nm and longer than 4.0 um : 1500nm", + ) + + # via3 + via3.width(nm(70), euclidian).output( + "VIA3.1", "VIA3.1 : Minimum/Maximum width of via3 : 70nm" + ) + via3.space(nm(85), euclidian).output( + "VIA3.2", "VIA3.2 : Minimum spacing of via3 : 85nm" + ) + via3.not_(metal3).output("VIA3.3", "VIA3.3 : via3 must be inside metal3") + via3.not_(metal4).output("VIA3.4", "VIA3.4 : via3 must be inside metal4") + + # metal4 + metal4.width(nm(140), euclidian).output( + "METAL4.1", "METAL4.1 : Minimum width of semi-global metal4 : 140nm" + ) + metal4.space(nm(140), euclidian).output( + "METAL4.2", "METAL4.2 : Minimum spacing of semi-global metal4 : 140nm" + ) + metal4_gt270, metal4_gt500, metal4_gt900 = classify_by_width( + deck, metal4, nm(270), nm(500), nm(900) + ) + metal4_gt270.edges().with_length(nm(900), None).space( + nm(270), euclidian + ).output( + "METAL4.6", + "METAL4.6 : Minimum spacing of semi-global metal4 wider than 270 nm and longer than 900 nm : 270nm", + ) + metal4_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL4.7", + "METAL4.7 : Minimum spacing of semi-global metal4 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal4_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL4.8", + "METAL4.8 : Minimum spacing of semi-global meta4l wider than 900 nm and longer than 2.7 um : 900nm", + ) + + # via4 + via4.width(nm(140), euclidian).output( + "VIA4.1", "VIA4.1 : Minimum width of via4 : 140nm" + ) + via4.space(nm(160), euclidian).output( + "VIA4.2", "VIA4.2 : Minimum spacing of via4 : 160nm" + ) + via4.not_(metal4).output("VIA4.3", "VIA4.3 : via4 must be inside metal4") + via4.not_(metal5).output("VIA4.4", "VIA4.4 : via4 must be inside metal5") + + # metal5 + metal5.width(nm(140), euclidian).output( + "METAL5.1", "METAL5.1 : Minimum width of semi-global metal5 : 140nm" + ) + metal5.space(nm(140), euclidian).output( + "METAL5.2", "METAL5.2 : Minimum spacing of semi-global metal5 : 140nm" + ) + metal5_gt270, metal5_gt500, metal5_gt900 = classify_by_width( + deck, metal5, nm(270), nm(500), nm(900) + ) + metal5_gt270.edges().with_length(nm(900), None).space( + nm(270), euclidian + ).output( + "METAL5.6", + "METAL5.6 : Minimum spacing of semi-global metal5 wider than 270 nm and longer than 900 nm : 270nm", + ) + metal5_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL5.7", + "METAL5.7 : Minimum spacing of semi-global metal5 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal5_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL5.8", + "METAL5.8 : Minimum spacing of semi-global meta5l wider than 900 nm and longer than 2.7 um : 900nm", + ) + + # via5 + via5.width(nm(140), euclidian).output( + "VIA5.1", "VIA5.1 : Minimum/Maximum width of via5 : 140nm" + ) + via5.space(nm(160), euclidian).output( + "VIA5.2", "VIA5.2 : Minimum spacing of via5 : 160nm" + ) + via5.not_(metal5).output("VIA5.3", "VIA5.3 : via5 must be inside metal5") + via5.not_(metal6).output("VIA5.4", "VIA5.4 : via5 must be inside metal6") + + # metal6 + metal6.width(nm(140), euclidian).output( + "METAL6.1", "METAL6.1 : Minimum width of semi-global metal6 : 140nm" + ) + metal6.space(nm(140), euclidian).output( + "METAL6.2", "METAL6.2 : Minimum spacing of semi-global metal6 : 140nm" + ) + metal6_gt270, metal6_gt500, metal6_gt900 = classify_by_width( + deck, metal6, nm(270), nm(500), nm(900) + ) + metal6_gt270.edges().with_length(nm(900), None).space( + nm(270), euclidian + ).output( + "METAL6.6", + "METAL6.6 : Minimum spacing of semi-global metal6 wider than 270 nm and longer than 900 nm : 270nm", + ) + metal6_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL6.7", + "METAL6.7 : Minimum spacing of semi-global metal6 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal6_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL6.8", + "METAL6.8 : Minimum spacing of semi-global metal6 wider than 900 nm and longer than 2.7 um : 900nm", + ) + + # via6 + via6.width(nm(140), euclidian).output( + "VIA6.1", "VIA6.1 : Minimum/Maximum width of via6 : 140nm" + ) + via6.space(nm(160), euclidian).output( + "VIA6.2", "VIA6.2 : Minimum spacing of via6 : 160nm" + ) + via6.not_(metal6).output("VIA6.3", "VIA6.3 : via6 must be inside metal6") + via6.not_(metal7).output("VIA6.4", "VIA6.4 : via6 must be inside metal7") + + # metal7 + metal7.width(nm(400), euclidian).output( + "METAL7.1", "METAL7.1 : Minimum width of thin global metal7 : 400nm" + ) + metal7.space(nm(400), euclidian).output( + "METAL7.2", "METAL7.2 : Minimum spacing of thin global metal7 : 400nm" + ) + metal7_gt500, metal7_gt900, metal7_gt1500 = classify_by_width( + deck, metal7, nm(500), nm(900), nm(1500) + ) + metal7_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL7.7", + "METAL7.7 : Minimum spacing of thin global metal7 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal7_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL7.8", + "METAL7.8 : Minimum spacing of thin global metal7 wider than 900 nm and longer than 2.7 um : 900nm", + ) + metal7_gt1500.edges().with_length(um(4), None).space( + nm(1500), euclidian + ).output( + "METAL7.9", + "METAL7.9 : Minimum spacing of thin global meta7l wider than 1500 nm and longer than 4.0 um : 1500nm", + ) + + # via7 + via7.width(nm(400), euclidian).output( + "VIA6.1", "VIA6.1 : Minimum/Maximum width of via7 : 400nm" + ) + via7.space(nm(440), euclidian).output( + "VIA6.2", "VIA6.2 : Minimum spacing of via7 : 440nm" + ) + via7.not_(metal7).output("VIA7.3", "VIA7.3 : via7 must be inside metal7") + via7.not_(metal8).output("VIA7.4", "VIA7.4 : via7 must be inside metal8") + + # metal8 + metal8.width(nm(400), euclidian).output( + "METAL8.1", "METAL8.1 : Minimum width of thin global metal8 : 400nm" + ) + metal8.space(nm(400), euclidian).output( + "METAL8.2", "METAL8.2 : Minimum spacing of thin global metal8 : 400nm" + ) + metal8_gt500, metal8_gt900, metal8_gt1500 = classify_by_width( + deck, metal8, nm(500), nm(900), nm(1500) + ) + metal8_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL8.7", + "METAL8.7 : Minimum spacing of thin global metal8 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal8_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL8.8", + "METAL8.8 : Minimum spacing of thin global metal8 wider than 900 nm and longer than 2.7 um : 900nm", + ) + metal8_gt1500.edges().with_length(um(4), None).space( + nm(1500), euclidian + ).output( + "METAL8.9", + "METAL8.9 : Minimum spacing of thin global metal8 wider than 1500 nm and longer than 4.0 um : 1500nm", + ) + + # via8 + via8.width(nm(400), euclidian).output( + "VIA8.1", "VIA8.1 : Minimum/Maximum width of via8 : 400nm" + ) + via8.space(nm(440), euclidian).output( + "VIA8.2", "VIA8.2 : Minimum spacing of via8 : 440nm" + ) + via8.not_(metal8).output("VIA8.3", "VIA8.3 : via8 must be inside metal8") + via8.not_(metal9).output("VIA8.4", "VIA8.4 : via8 must be inside metal9") + + # metal9 + metal9.width(nm(800), euclidian).output( + "METAL9.1", "METAL9.1 : Minimum width of global metal9 : 800nm" + ) + metal9.space(nm(800), euclidian).output( + "METAL9.2", "METAL9.2 : Minimum spacing of global metal9 : 800nm" + ) + metal9_gt500, metal9_gt900, metal9_gt1500 = classify_by_width( + deck, metal9, nm(500), nm(900), nm(1500) + ) + metal9_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL9.7", + "METAL9.7 : Minimum spacing of global metal9 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal9_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL9.8", + "METAL9.8 : Minimum spacing of global metal9 wider than 900 nm and longer than 2.7 um : 900nm", + ) + metal9_gt1500.edges().with_length(um(4), None).space( + nm(1500), euclidian + ).output( + "METAL9.9", + "METAL9.9 : Minimum spacing of global metal9 wider than 1500 nm and longer than 4.0 um : 1500nm", + ) + + # via9 + via9.width(nm(800), euclidian).output( + "VIA9.1", "VIA9.1 : Minimum/Maximum width of via9 : 800nm" + ) + via9.space(nm(880), euclidian).output( + "VIA9.2", "VIA9.2 : Minimum spacing of via9 : 880nm" + ) + via9.not_(metal9).output("VIA9.3", "VIA9.3 : via9 must be inside metal9") + via9.not_(metal10).output("VIA9.4", "VIA9.4 : via9 must be inside metal10") + + # metal10 + metal10.width(nm(800), euclidian).output( + "METAL10.1", "METAL10.1 : Minimum width of global metal10 : 800nm" + ) + metal10.space(nm(800), euclidian).output( + "METAL10.2", "METAL10.2 : Minimum spacing of global metal10 : 800nm" + ) + metal10_gt500, metal10_gt900, metal10_gt1500 = classify_by_width( + deck, metal10, nm(500), nm(900), nm(1500) + ) + metal10_gt500.edges().with_length(um(1.8), None).space( + nm(500), euclidian + ).output( + "METAL10.7", + "METAL10.7 : Minimum spacing of global metal10 wider than 500 nm and longer than 1.8 um : 500nm", + ) + metal10_gt900.edges().with_length(um(2.7), None).space( + nm(900), euclidian + ).output( + "METAL10.8", + "METAL10.8 : Minimum spacing of global metal10 wider than 900 nm and longer than 2.7 um : 900nm", + ) + metal10_gt1500.edges().with_length(um(4), None).space( + nm(1500), euclidian + ).output( + "METAL10.9", + "METAL10.9 : Minimum spacing of global metal10 wider than 1500 nm and longer than 4.0 um : 1500nm", + ) + + # Manufacturing grid check + # https://www.eda.ncsu.edu/wiki/FreePDK45:Manufacturing_Grid + + if OFFGRID: + deck.info("Off-grid checks") + + # grid = nm(2.5) + + # A Ruby idiom to get the value of a variable whose name is in "dwg" (as symbol) + # TODO(rovinski) local_variable_get is not compatible with older Ruby versions + # all_drawing.each do |dwg| + # layer = binding.local_variable_get(dwg) + # r_grid = layer.ongrid(grid).polygons(10.nm) + # r_grid.output("GRID: vertexes on layer #{dwg} not on grid of #{'%.12g' % grid}") + # end + + # Antenna checks + + if ANTENNA: + deck.info("ANTENNA checks") + + # Create layer connections + deck.connect(gate, poly) + deck.connect(poly, cont) + deck.connect(cont, metal1) + deck.connect(metal1, via1) + deck.connect(via1, metal2) + deck.connect(metal2, via2) + deck.connect(via2, metal3) + deck.connect(metal3, via3) + deck.connect(via3, metal4) + deck.connect(metal4, via4) + deck.connect(via4, metal5) + deck.connect(metal5, via5) + deck.connect(via5, metal6) + deck.connect(metal6, via6) + deck.connect(via6, metal7) + deck.connect(metal7, via7) + deck.connect(via7, metal8) + deck.connect(metal8, via8) + deck.connect(via8, metal9) + deck.connect(metal9, via9) + deck.connect(via9, metal10) + + # Create and connect diodes + diode = nplus & active - nwell + deck.connect(diode, cont) + + deck.antenna_check(gate, metal1, 300.0, diode).output( + "METAL1_ANTENNA", + "METAL1_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + deck.antenna_check(gate, metal2, 300.0, diode).output( + "METAL2_ANTENNA", + "METAL2_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + deck.antenna_check(gate, metal3, 300.0, diode).output( + "METAL3_ANTENNA", + "METAL3_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + deck.antenna_check(gate, metal4, 300.0, diode).output( + "METAL4_ANTENNA", + "METAL4_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + deck.antenna_check(gate, metal5, 300.0, diode).output( + "METAL5_ANTENNA", + "METAL5_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + deck.antenna_check(gate, metal6, 300.0, diode).output( + "METAL6_ANTENNA", + "METAL6_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + deck.antenna_check(gate, metal7, 300.0, diode).output( + "METAL7_ANTENNA", + "METAL7_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + deck.antenna_check(gate, metal8, 300.0, diode).output( + "METAL8_ANTENNA", + "METAL8_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + deck.antenna_check(gate, metal9, 300.0, diode).output( + "METAL9_ANTENNA", + "METAL9_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + deck.antenna_check(gate, metal10, 300.0, diode).output( + "METAL10_ANTENNA", + "METAL10_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1", + ) + + # Remove connections + deck.clear_connections() From f2ac2a69f32504fef5e52e65708c0d167e970d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 08:41:53 +0100 Subject: [PATCH 04/11] sky130hd: DRC deck in Python MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, make drc on sky130hd needed the KLayout application to run sky130hd.lydrc. Now it runs sky130hd_drc.py on the klayout Python module. The Ruby deck's "if backend_flow = CU" blocks are assignments, not comparisons: with CU false they never run and the AL blocks always do. The Python deck says that with one CU = False. Its FEOL rules stay off as before; the cell selections (source.select) are a Source in klayout_drc.py with the same select/unselect order on the shape iterator. The deck's area(layer) is the layer's area in square microns, as the DRC language's. Checked against the Ruby deck run by the KLayout 0.30.0 application on the same sky130hd/gcd GDS, Python on klayout 0.30.13: clean GDS: 0 violations, both seeded GDS: 6 violations in 6 categories, both, same markers (narrow and close m1, narrow li and m2, via without metal) FEOL on in both decks, seeded GDS: 6 and 6, same markers runtime: 4.2 s Ruby, 2.7 s Python (FEOL on: 177 s, 162 s) sky130hd has no density rules in its deck; the tiled density checks are in ihp-sg13g2's. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- flow/platforms/sky130hd/config.mk | 2 +- flow/platforms/sky130hd/drc/sky130hd.lydrc | 918 ---------- flow/platforms/sky130hd/drc/sky130hd_drc.py | 1825 +++++++++++++++++++ flow/util/klayout_drc.py | 4 +- 4 files changed, 1829 insertions(+), 920 deletions(-) delete mode 100644 flow/platforms/sky130hd/drc/sky130hd.lydrc create mode 100644 flow/platforms/sky130hd/drc/sky130hd_drc.py diff --git a/flow/platforms/sky130hd/config.mk b/flow/platforms/sky130hd/config.mk index 8aaa4ce0b7..b17ba06ef3 100644 --- a/flow/platforms/sky130hd/config.mk +++ b/flow/platforms/sky130hd/config.mk @@ -147,7 +147,7 @@ export GND_NETS_VOLTAGES ?= VSS 0.0 export IR_DROP_LAYER ?= met1 # DRC Check -export KLAYOUT_DRC_FILE = $(PLATFORM_DIR)/drc/$(PLATFORM).lydrc +export KLAYOUT_DRC_FILE ?= $(PLATFORM_DIR)/drc/$(PLATFORM)_drc.py #LVS Check export CDL_FILE = $(PLATFORM_DIR)/cdl/$(PLATFORM).cdl diff --git a/flow/platforms/sky130hd/drc/sky130hd.lydrc b/flow/platforms/sky130hd/drc/sky130hd.lydrc deleted file mode 100644 index e1a1bbb472..0000000000 --- a/flow/platforms/sky130hd/drc/sky130hd.lydrc +++ /dev/null @@ -1,918 +0,0 @@ - - - - - drc - - - - false - false - - true - drc_scripts - tools_menu.drc.end - dsl - drc-dsl-xml - # -# DRC for SKY130 according to : -# https://skywater-pdk.readthedocs.io/en/latest/rules/periphery.html -# https://skywater-pdk.readthedocs.io/en/latest/rules/layers.html -# -# Distributed under GNU GPLv3: https://www.gnu.org/licenses/ -# -# History : -# 2020-10-04 : v1.0 : initial release -# -########################################################################################## - -# optionnal for a batch launch : klayout -b -rd in_gds=my_layout.gds -rd report_file=6_drc_count.rpt -r drc_sky130.drc -if $in_gds - source($in_gds) -end - -if $report_file - report("SKY130 DRC runset", $report_file) -else - report("SKY130 DRC runset", File.join(File.dirname(RBA::CellView::active.filename), "6_drc_count.rpt")) -end - -AL = true # do not change -CU = false # do not change -# choose betwen only one of AL or CU back-end flow here : -backend_flow = AL - -# enable / disable rule groups -FEOL = false # front-end-of-line checks -BEOL = true # back-end-of-line checks -OFFGRID = true # manufacturing grid/angle checks - -# klayout setup -######################## -# use a tile size of 1mm - not used in deep mode- -tiles(1000.um) -# use a tile border of 10 micron: -tile_borders(1.um) -#no_borders - -# hierachical -deep - -# use 4 cpu cores -threads(4) -# if more inof is needed, set true -verbose(true) - -# layers definitions -######################## - -# all except purpose (datatype) 5 -- label and 44 -- via -li_wildcard = "67/0-4,6-43,45-*" -mcon_wildcard = "67/44" - -m1_wildcard = "68/0-4,6-43,45-*" -via_wildcard = "68/44" - -m2_wildcard = "69/0-4,6-43,45-*" -via2_wildcard = "69/44" - -m3_wildcard = "70/0-4,6-43,45-*" -via3_wildcard = "70/44" - -m4_wildcard = "71/0-4,6-43,45-*" -via4_wildcard = "71/44" - -m5_wildcard = "72/0-4,6-43,45-*" - -diff = input(65, 20) -tap = polygons(65, 44) -nwell = polygons(64, 20) -dnwell = polygons(64, 18) -pwbm = polygons(19, 44) -pwde = polygons(124, 20) -natfet = polygons(124, 21) -hvtr = polygons(18, 20) -hvtp = polygons(78, 44) -ldntm = polygons(11, 44) -hvi = polygons(75, 20) -tunm = polygons(80, 20) -lvtn = polygons(125, 44) -poly = polygons(66, 20) -hvntm = polygons(125, 20) -nsdm = polygons(93, 44) -psdm = polygons(94, 20) -rpm = polygons(86, 20) -urpm = polygons(79, 20) -npc = polygons(95, 20) -licon = polygons(66, 44) - -li = polygons(li_wildcard) -mcon = polygons(mcon_wildcard) - -m1 = polygons(m1_wildcard) -via = polygons(via_wildcard) - -m2 = polygons(m2_wildcard) -via2 = polygons(via2_wildcard) - -m3 = polygons(m3_wildcard) -via3 = polygons(via3_wildcard) - -m4 = polygons(m4_wildcard) -via4 = polygons(via4_wildcard) - -m5 = polygons(m5_wildcard) - -pad = polygons(76, 20) -nsm = polygons(61, 20) -capm = polygons(89, 44) -cap2m = polygons(97, 44) -vhvi = polygons(74, 21) -uhvi = polygons(74, 22) -npn = polygons(82, 20) -inductor = polygons(82, 24) -vpp = polygons(82, 64) -pnp = polygons(82, 44) -lvs_prune = polygons(84, 44) -ncm = polygons(92, 44) -padcenter = polygons(81, 20) -mf = polygons(76, 44) -areaid_sl = polygons(81, 1) -areaid_ce = polygons(81, 2) -areaid_fe = polygons(81, 3) -areaid_sc = polygons(81, 4) -areaid_sf = polygons(81, 6) -areaid_sw = polygons(81, 7) -areaid_sr = polygons(81, 8) -areaid_mt = polygons(81, 10) -areaid_dt = polygons(81, 11) -areaid_ft = polygons(81, 12) -areaid_ww = polygons(81, 13) -areaid_ld = polygons(81, 14) -areaid_ns = polygons(81, 15) -areaid_ij = polygons(81, 17) -areaid_zr = polygons(81, 18) -areaid_ed = polygons(81, 19) -areaid_de = polygons(81, 23) -areaid_rd = polygons(81, 24) -areaid_dn = polygons(81, 50) -areaid_cr = polygons(81, 51) -areaid_cd = polygons(81, 52) -areaid_st = polygons(81, 53) -areaid_op = polygons(81, 54) -areaid_en = polygons(81, 57) -areaid_en20 = polygons(81, 58) -areaid_le = polygons(81, 60) -areaid_hl = polygons(81, 63) -areaid_sd = polygons(81, 70) -areaid_po = polygons(81, 81) -areaid_it = polygons(81, 84) -areaid_et = polygons(81, 101) -areaid_lvt = polygons(81, 108) -areaid_re = polygons(81, 125) -areaid_ag = polygons(81, 79) -poly_rs = polygons(66, 13) -diff_rs = polygons(65, 13) -pwell_rs = polygons(64, 13) -li_rs = polygons(67, 13) -cfom = polygons(22, 20) - - -# Define a new custom function that selects polygons by their number of holes: -# It will return a new layer containing those polygons with min to max holes. -# max can be nil to omit the upper limit. -class DRC::DRCLayer - def with_holes(min, max) - new_data = RBA::Region::new - self.data.each do |p| - if p.holes >= (min || 0) && (!max || p.holes <= max) - new_data.insert(p) - end - end - DRC::DRCLayer::new(@engine, new_data) - end -end - -# DRC section -######################## -info("DRC section") - -if FEOL -info("FEOL section") -gate = diff & poly - -# dnwell -dnwell.width(3.0, euclidian).output("dnwell.2", "dnwell.2 : min. dnwell width : 3.0um") -dnwell.not(uhvi).not(areaid_po).isolated(6.3, euclidian).output("dnwell.3", "dnwell.3 : min. dnwell spacing : 6.3um") -dnwell.and(pnp).output("dnwell.4", "dnwell.4 : dnwell must not overlap pnp") -dnwell.and(psdm).edges.not(psdm.edges).output("dnwell.5", "p+ must not straddle dnwell") -# dnwell.6 rue not coded - -# nwell -nwell.width(0.84, euclidian).output("nwell.1", "nwell.1 : min. nwell width : 0.84um") -nwell.isolated(1.27, euclidian).output("nwell.2a", "nwell.2a : min. nwell spacing (merged if less) : 1.27um") -# rule nwell.4 is suitable for digital cells -#nwell.not(uhvi).not(areaid_en20).not_interacting(tap.and(licon).and(li)).output("nwell.4", "nwell4 : all nwell exempt inside uhvi must contain a n+tap") -nwell.enclosing(dnwell.not(uhvi).not(areaid_po), 0.4, euclidian).output("nwell.5", "nwell.5 : min. nwell enclosing dnwell exempt unside uhvi : 0.4um") -dnwell.enclosing(nwell.not(uhvi), 1.03, euclidian).output("nwell.6", "nwell.6 : min. dnwell enclosing nwell exempt unside uhvi : 1.03um") -dnwell.separation(nwell, 4.5, euclidian).output("nwell.7", "nwell.7 : min. dnwell separation nwell : 4.5um") - -# pwbm -pwbm.not(uhvi).output("pwbm", "pwbm must be inside uhvi") -dnwell.and(uhvi).edges.not(pwbm).output("pwbm.4", "pwbm.4 : dnwell inside uhvi must be enclosed by pwbm") - -# pwde -pwde.not(pwbm).output("pwdem.3", "pwdem.3 : pwde must be inside pwbm") -pwde.not(uhvi).output("pwdem.4", "pwdem.4 : pwde must be inside uhvi") -pwde.not(dnwell).output("pwdem.5", "pwdem.5 : pwde must be inside dnwell") - -# hvtp -#hvtp.not(nwell).output("hvtp", "hvtp must inside nwell") -hvtp.width(0.38, euclidian).output("hvtp.1", "hvtp.1 : min. hvtp width : 0.38um") -hvtp.isolated(0.38, euclidian).output("hvtp.2", "hvtp.2 : min. hvtp spacing : 0.38um") -hvtp.enclosing(gate.and(psdm), 0.18, euclidian).output("hvtp.3", "hvtp.3 : min. hvtp enclosure of pfet gate : 0.18um") -hvtp.separation(gate.and(psdm), 0.18, euclidian).output("hvtp.4", "hvtp.4 : min. hvtp spacing pfet gate: 0.18um") -hvtp.with_area(0..0.265).output("hvtp.5", "hvtp.5 : min. hvtp area : 0.265um²") - -# hvtr -hvtr.width(0.38, euclidian).output("hvtr.1", "hvtr.1 : min. hvtr width : 0.38um") -hvtr.isolated(0.38, euclidian).output("hvtr.2", "hvtr.2 : min. hvtr spacing : 0.38um") - -# lvtn -lvtn.width(0.38, euclidian).output("lvtn.1", "lvtn.1 : min. lvtn width : 0.38um") -lvtn.isolated(0.38, euclidian).output("lvtn.2", "lvtn.2 : min. lvtn spacing : 0.38um") -lvtn.separation(diff.and(poly).not(uhvi), 0.18, euclidian).output("lvtn.3a", "lvtn.3a : min. lvtn spacing to gate : 0.18um") -lvtn.separation(diff.and(nwell).not(poly), 0.235, projection).output("lvtn.3b", "lvtn.3b : min. lvtn spacing to pfet s/d : 0.18um") -lvtn.enclosing(gate.not(uhvi), 0.18, euclidian).output("lvtn.4b", "lvtn.4b : min. lvtn enclosing to gate : 0.18um") -lvtn.separation(hvtp, 0.38, euclidian).output("lvtn.9", "lvtn.9 : min. lvtn spacing hvtp : 0.38um") -nwell.not_interacting(gate.and(nwell.not(hvi).not(areaid_ce))).enclosing(lvtn.not(uhvi), 0.18, euclidian).polygons.without_area(0).output("lvtn.4b", "lvtn.4b : min. lvtn enclosure of gate : 0.18um") -lvtn.separation(nwell.inside(areaid_ce), 0.38, euclidian).output("lvtn.12", "lvtn.12 : min. lvtn spacing nwell inside areaid.ce : 0.38um") -lvtn.with_area(0..0.265).output("lvtn.13", "lvtn.13 : min. lvtn area : 0.265um²") - -# ncm -ncm.and(tap.and(nwell).or(diff.not(nwell))).output("ncm.x.3", "ncm.x.3 : ncm must not overlap n+diff") -ncm.width(0.38, euclidian).output("ncm.1", "ncm.1 : min. ncm width : 0.38um") -ncm.isolated(0.38, euclidian).output("ncm.2", "ncm.2 : min. ncm spacing manual merge if smaller : 0.38um") -ncm.enclosing(diff.and(nwell), 0.18, euclidian).output("ncm.3", "ncm.3 : min. ncm enclosure of p+diff : 0.18um") -ncm.separation(lvtn.and(diff), 0.23, euclidian).output("ncm.5", "ncm.5 : min. ncm spacing lvtn diff : 0.23um") -ncm.separation(diff.not(nwell), 0.2, euclidian).output("ncm.6", "ncm.6 : min. ncm spacing nfet : 0.2um") -ncm.with_area(0..0.265).output("ncm.7", "ncm.13 : min. ncm area : 0.265um²") - -# diff-tap -difftap = diff + tap -difftap.width(0.15, euclidian).output("difftap.1", "difftap.1 : min. difftap width : 0.15um") -not_in_cell1 = layout(source.cell_obj).select("s8cell_ee_plus_sseln_a", "-s8cell_ee_plus_sseln_b", "-s8cell_ee_plus_sselp_a", "-s8cell_ee_plus_sselp_b" , "-s8fpls_pl8", "-s8fpls_rdrv4" , "-s8fpls_rdrv4f", "-s8fpls_rdrv8") -not_in_cell1_diff = not_in_cell1.input(65, 20) -not_in_cell1_diff.not(areaid_sc).not(poly).edges.and(gate.edges).with_length(0,0.42).output("difftap.2", "difftap.2: min. gate (exempt areaid.sc) width : 0.42um") -diff.and(areaid_sc).not(poly).edges.and(gate.edges).with_length(0,0.36).output("difftap.2", "difftap.2: min. gate inside areaid.sc width : 0.36um") -difftap.isolated(0.27, euclidian).output("difftap.3", "difftap.3 : min. difftap spacing : 0.27um") -tap.edges.and(diff.edges).with_length(0,0.29).output("difftap.4", "difftap.4 : min. tap bound by diffusion : 0.29um") -tap.edges.and(diff.edges).space(0.4, projection).output("difftap.5", "difftap.5 : min. tap bound by 2 diffusions : 0.4um") -(tap.edges.and(diff.edges)).extended(0.01, 0.01, 0, 0, false).not(tap.and(diff)).and(diff.or(tap)).output("difftap.6", "difftap.6 : diff and tap not allowed to extend beyong their abutting ege") -tap.edges.not_interacting(diff.edges).separation(diff.edges, 0.13, euclidian).output("difftap.7", "difftap.7 : min. diff/tap spacing to non-coincident diff edge : 0.13um") -diff.edges.not_interacting(tap.edges).separation(tap.edges, 0.13, euclidian).output("difftap.7", "difftap.7 : min. diff/tap spacing to non-coincident tap edge : 0.13um") -nwell.enclosing(diff.not(uhvi).and(psdm), 0.18, euclidian).output("difftap.8", "difftap.8 : min. p+diff enclosure by nwell : 0.18um") -diff.not(uhvi).and(nsdm).separation(nwell, 0.34, euclidian).output("difftap.9", "difftap.9 : min. n+diff spacing to nwell : 0.34um") -nwell.enclosing(tap.not(uhvi).and(nsdm), 0.18, euclidian).output("difftap.10", "difftap.10 : min. n+tap enclosure by nwell : 0.18um") -tap.not(uhvi).and(psdm).separation(nwell, 0.13, euclidian).output("difftap.11", "difftap.11 : min. p+tap spacing to nwell : 0.13um") - -# tunm -tunm.width(0.41, euclidian).output("tunm.1", "tunm.1 : min. tunm width : 0.41um") -tunm.isolated(0.5, euclidian).output("tunm.2", "tunm.2 : min. tunm spacing : 0.5um") -tunm.enclosing(gate, 0.095, euclidian).output("tunm.3", "tunm.3 : min. tunm beyond gate : 0.095um") -tunm.separation(gate.not_interacting(tunm), 0.095, euclidian).output("tunm.4", "tunm.4 : min. tunm spacing to gate outside tunm: 0.095um") -gate.and(tunm).edges.not(gate.edges).output("tunm.5", "tunm.5 : gate must not straddle tunm") -tunm.not(dnwell).output("tunm.6a", "tunm.6a : tunm not allowed outside dnwell") -tunm.with_area(0..0.672).output("tunm.7", "tunm.7 : min. tunm area : 0.672um²") - -# poly -poly.width(0.15, euclidian).output("poly.1a", "poly.1a : min. poly width : 0.15um") -poly.not(diff).edges.and(gate.and(lvtn).edges).space(0.35, euclidian).output("poly.1b", "poly.1b: min. lvtn gate width : 0.35um") -poly.isolated(0.21, euclidian).output("poly.2", "poly.2 : min. poly spacing : 0.21um") -poly.and(rpm.or(urpm).or(poly_rs)).width(0.33, euclidian).output("poly.3", "poly.3 : min. poly resistor width : 0.33um") -poly.not(gate).separation(diff, 0.075, projection).polygons.without_area(0).output("poly.4", "poly.4 : min. poly on field spacing to diff : 0.075um") -poly.not(gate).separation(tap, 0.055, euclidian).output("poly.5", "poly.5 : min. poly on field spacing to tap : 0.055um") -gate.separation(tap, 0.3, projection).polygons.and(diff).output("poly.6", "poly.6 : min. gate spacing to tap : 0.3um") -diff.enclosing(gate, 0.25, projection).polygons.without_area(0).output("poly.7", "poly.7 : min. source/drain length : 0.25um") -poly.enclosing(gate, 0.13, projection).polygons.without_area(0).output("poly.8", "poly.8 : min. poly extention gate (endcap) : 0.13um") -poly.and(rpm.or(urpm).or(poly_rs)).separation(poly.or(difftap), 0.48, euclidian).polygons.without_area(0).output("poly.9", "poly.9 : min. poly resistor space to poly or diff/tap : 0.48um") -diff.merged.edges.end_segments(0.01).and(poly).output("poly.10", "poly.10 : poly must not overlap diff corner") -gate.with_angle(0 .. 90).output("poly.11", "poly.11 : non 90 degree angle gate") -not_in_cell3 = layout(source.cell_obj).select("s8fgvr_n_fg2") -not_in_cell3_poly = not_in_cell3.input(66, 20) -not_in_cell3_poly.not(hvi).not(nwell.not(hvi)).and(tap).output("poly.12", "poly.12 : poly must not overlap tap") -poly.and(diff_rs).output("poly.15", "poly.15 : poly must not overlap diff resistor") - -# rpm -rpm.width(1.27, euclidian).output("rpm.1a", "rpm.1a : min. rpm width : 1.27um") -rpm.isolated(0.84, euclidian).output("rpm.2", "rpm.2 : min. rpm spacing : 0.84um") -rpm.enclosing(poly.and(poly_rs).and(psdm), 0.2, euclidian).output("rpm.3", "rpm.3 : min. rpm enclosure of poly resistor : 0.2um") -psdm.enclosing(poly.and(poly_rs).and(rpm), 0.11, euclidian).output("rpm.4", "rpm.4 : min. psdm enclosure of poly resistor : 0.11um") -npc.enclosing(poly.and(poly_rs).and(rpm), 0.095, euclidian).output("rpm.5", "rpm.5 : min. npc enclosure of poly resistor : 0.095um") -rpm.separation(nsdm, 0.2, euclidian).output("rpm.6", "rpm.6 : min. rpm spacing nsdm: 0.2um") -rpm.separation(poly, 0.2, euclidian).output("rpm.7", "rpm.7 : min. rpm spacing poly: 0.2um") -rpm.and(poly).edges.not(poly.edges).output("rpm.8", "rpm.8 : poly must not straddle rpm") -poly.and(poly_rs).and(rpm).separation(hvntm, 0.185, euclidian).output("rpm.9", "rpm.9 : min. poly resistor spacing hvntm: 0.185um") -rpm.and(pwbm).output("rpm.10", "rpm.107 : min. rpm spacing pwbm: na") - -# varac -varac = poly & tap & (nwell - hvi) - areaid_ce -tap.not(poly).edges.and(varac.edges).space(0.18, euclidian).output("varac.1", "varac.1: min. varac channel length : 0.18um") -tap.and(poly).edges.and(varac.edges).space(1.0, euclidian).output("varac.2", "varac.2: min. varac channel wdth : 1.0um") -varac.separation(hvtp, 0.18, euclidian).output("varac.3", "varac.3: min. varac channel space to hvtp : 0.18um") -varac.separation(licon.and(tap), 0.25, euclidian).output("varac.4", "varac.4: min. varac channel space to licon on tap : 0.25um") -nwell.enclosing(poly.overlapping(varac), 0.15, euclidian).output("varac.5", "varac.5: min. nwell enclosure of poly overlapping varac channel : 0.15um") -tap.overlapping(varac).separation(difftap, 0.27, euclidian).polygons.without_area(0).output("varac.6", "varac.6: min. varac channel tap space to difftap : 0.27um") -nwell.overlapping(varac).and(diff.and(nwell)).output("varac.7", "varac.7: nwell overlapping varac channel must not overlap p+diff") - -# photo -photodiode = dnwell & areaid_po -photodiode.edges.without_length(3.0).output("photo.2", "photo.2 : minimum/maximum width of photodiode : 3.0um") -photodiode.isolated(5.0, euclidian).output("photo.3", "photo.3 : mini. photodiode spacing : 5.0um") -photodiode.separation(dnwell, 5.3, euclidian).output("photo.4", "photo.4 : mini. photodiode spacing to dnwell : 5.3um") -areaid_po.not(dnwell).output("photo.5.6", "photo.5.6 : photodiode edges must coincide areaid.po and enclosed by dnwell") -photodiode.not(tap.not(nwell).holes).output("photo.7", "photo.7 : photodiode must be enclosed by p+tap ring") -photodiode.and(nwell).edges.without_length(0.84).output("photo.8", "photo.8 : minimum/maximum width of nwell inside photodiode : 0.84um") -areaid_po.edges.and(photodiode.and(nwell).sized(1.08)).without_length(12.0).output("photo.9", "photo.9 : minimum/maximum enclosure of nwell by photodiode : 1.08um") -photodiode.and(tap).edges.without_length(0.41).output("photo.10", "photo.10 : minimum/maximum width of tap inside photodiode : 0.41um") - -# npc -npc.width(0.27, euclidian).output("npc.1", "npc.1 : min. npc width : 0.27um") -npc.isolated(0.27, euclidian).output("npc.2", "npc.2 : min. npc spacing, should be mnually merge if less : 0.27um") -npc.separation(gate, 0.09, euclidian).output("npc.4", "npc.4 : min. npc spacing to gate : 0.09um") - -# nsdm/psdm -npsdm = nsdm + psdm -nsdm.width(0.38, euclidian).output("nsdm.1", "nsdm.1 : min. nsdm width : 0.38um") -psdm.width(0.38, euclidian).output("psdm.1", "psdm.1 : min. psdm width : 0.38um") -nsdm.isolated(0.38, euclidian).output("n/psdm.1", "n/psdm.1 : min. nsdm spacing, should be mnually merge if less : 0.38um") -psdm.isolated(0.38, euclidian).output("n/psdm.1", "n/psdm.1 : min. psdm spacing, should be mnually merge if less : 0.38um") -npsdm.enclosing(diff, 0.125, euclidian).polygons.not(tap.sized(0.125)).output("n/psdm.5a", "n/psdm.5a : min. n/psdm enclosure diff except butting edge : 0.125um") -npsdm.enclosing(tap, 0.125, euclidian).polygons.not(diff.sized(0.125)).output("n/psdm.5b", "n/psdm.5b : min. n/psdm enclosure tap except butting edge : 0.125um") -tap.edges.and(diff.edges).not(npsdm).output("n/psdm.6", "n/psdm.6 : min. n/psdm enclosure of butting edge : 0.0um") -nsdm.and(difftap).separation(psdm.and(difftap), 0.13, euclidian).polygons.without_area(0).output("n/psdm.7", "n/psdm.7 : min. nsdm diff spacing to psdm diff except butting edge : 0.13um") -diff.and((nsdm.and(nwell)).or(psdm.not(nwell))).output("n/psdm.8", "n/psdm.8 : diff should be the opposite type of well/substrate underneath") -tap.and((nsdm.not(nwell)).or(psdm.and(nwell))).output("n/psdm.8", "n/psdm.8 : tap should be the same type of well/substrate underneath") -tap.and(diff).without_area(0).output("tap and diff", "tap and diff must not overlap") -nsdm.with_area(0..0.265).output("n/psdm.10a", "n/psdm.10a : min. nsdm area : 0.265um²") -psdm.with_area(0..0.265).output("n/psdm.10b", "n/psdm.10b : min. psdm area : 0.265um²") - -# licon -licon.not(poly.interacting(poly_rs).and(rpm)).edges.without_length(0.17).output("licon.1", "licon.1 : minimum/maximum width of licon : 0.17um") -licon.and(poly.interacting(poly_rs).and(rpm)).not_interacting((licon.and(poly.interacting(poly_rs).and(rpm)).edges.with_length(0.19)).or(licon.and(poly.interacting(poly_rs).and(rpm)).edges.with_length(2.0))).output("licon.1b/c", "licon.1b/c : minimum/maximum width/length of licon inside poly resistor : 2.0/0.19um") -licon.isolated(0.17, euclidian).output("licon.2", "licon.2 : min. licon spacing : 0.17um") -licon.and(poly.interacting(poly_rs).and(rpm)).edges.with_length(0.19).space(0.35, euclidian).output("licon.2b", "licon.2b : min. licon 0.19um edge on resistor spacing : 0.35um") -licon.interacting(licon.and(poly.interacting(poly_rs).and(rpm)).edges.with_length(2.0)).separation(licon.and(poly.interacting(poly_rs).and(rpm)), 0.51, euclidian).output("licon.2c", "licon.2c : min. licon 2.0um edge on resistor spacing : 0.51um") -licon.and(poly.interacting(poly_rs).and(rpm)).separation(licon.not(poly.interacting(poly_rs).and(rpm)), 0.51, euclidian).output("licon.2d", "licon.2d : min. licon on resistor spacing other licon : 0.51um") -# rule licon.3 not coded -licon.not(li).not(poly.or(diff).or(tap)).output("licon.4", "licon.4 : min. licon must overlap li and (poly or tap or diff) ") -diff.enclosing(licon, 0.04, euclidian).output("licon.5", "licon.5 : min. diff enclosure of licon : 0.04um") -tap.edges.and(diff.edges).separation(licon.and(tap).edges, 0.06, euclidian).output("licon.6", "licon.6 : min. abutting edge spacing to licon tap : 0.06um") -licon_edges_with_less_enclosure_tap = tap.enclosing(licon, 0.12, projection).second_edges -opposite1 = (licon.edges - licon_edges_with_less_enclosure_tap).width(0.17 + 1.dbu, projection).polygons -licon.not_interacting(opposite1).output("licon.7", "licon.7 : min. tap enclosure of licon by one of 2 opposite edges : 0.12um") -poly.enclosing(licon, 0.05, euclidian).output("licon.8", "licon.8 : min. poly enclosure of licon : 0.05um") -licon008 = licon.interacting(poly.enclosing(licon, 0.08, euclidian).polygons) -licon_edges_with_less_enclosure_poly = poly.enclosing(licon, 0.08, projection).second_edges -opposite2 = (licon.edges - licon_edges_with_less_enclosure_poly).width(0.17 + 1.dbu, projection).polygons -licon008.not_interacting(opposite2).output("licon.8a", "licon.8a : min. poly enclosure of licon by one of 2 opposite edges : 0.08um") -# rule licon.9 not coded -licon.and(tap.and(nwell.not(hvi))).separation(varac, 0.25, euclidian).output("licon.10", "licon.10 : min. licon spacing to varac channel : 0.25um") -not_in_cell4 = layout(source.cell_obj).select("-s8fs_gwdlvx4", "-s8fs_gwdlvx8", "-s8fs_hvrsw_x4", "-s8fs_hvrsw8", "-s8fs_hvrsw264", "-s8fs_hvrsw520", "-s8fs_rdecdrv", "-s8fs_rdec8”, “s8fs_rdec32", "-s8fs_rdec264", "-s8fs_rdec520") -not_in_cell4_licon = not_in_cell4.input(66, 44) -not_in_cell4_licon.and(diff.or(tap)).separation(gate.not(areaid_sc), 0.055, euclidian).output("licon.11", "licon.11 : min. licon spacing to gate : 0.055um") -licon.and(diff.or(tap)).separation(gate.and(areaid_sc), 0.05, euclidian).output("licon.11a", "licon.11a : min. licon spacing to gate inside areaid.sc : 0.05um") -in_cell4 = layout(source.cell_obj).select("+s8fs_gwdlvx4", "+s8fs_gwdlvx8", "+s8fs_hvrsw_x4", "+s8fs_hvrsw8", "+s8fs_hvrsw264", "+s8fs_hvrsw520") -in_cell4_licon = in_cell4.input(66, 44) -in_cell4_licon.and(diff.or(tap)).separation(gate, 0.04, euclidian).output("licon.11c", "licon.11c : min. licon spacing to gate for specific cells: 0.04um") -# rules 11.b , 11.d not coded -diff.interacting(gate).not(diff.interacting(gate).width(5.7, euclidian).polygons).output("licon.12", "licon.12 : max. sd width without licon : 5.7um") -licon.and(diff.or(tap)).separation(npc, 0.09, euclidian).output("licon.13", "licon.13 : min. difftap licon spacing to npc : 0.09um") -licon.and(poly).separation(diff.or(tap), 0.19, euclidian).output("licon.14", "licon.14 : min. poly licon spacing to difftap : 0.19um") -npc.enclosing(licon.and(poly), 0.1, euclidian).output("licon.15", "licon.15 : min. npc enclosure of poly-licon : 0.1um") -# rule licon.16 not applicable for the diff for the nmos of a nand gates or the pmos of a nor gates -#diff.not(gate).not_interacting(licon).output("licon.16", "licon.16 : diff must enclose one licon") -tap.not(uhvi).not_interacting(licon).output("licon.16", "licon.16 : tap must enclose one licon") -poly.and(tap).edges.not(tap.edges).output("licon.17", "licon.17 : tap must not straddle poly") -npc.not_interacting(licon.and(poly)).output("licon.18", "licon.18 : npc mut enclosed one poly-licon") - -# vpp -vpp.width(1.43, euclidian).output("vpp.1", "vpp.1 : min. vpp width : 1.43um") -# rules 1.b, 1.c not coded -vpp.and(poly.or(difftap)).output("vpp.3", "vpp.3 : vpp must not overlapp poly or diff or tap") -vpp.and(nwell).edges.not(vpp.edges).output("vpp.4", "vpp.4 : vpp must not straddle nwell") -vpp.and(dnwell).edges.not(vpp.edges).output("vpp.4", "vpp.4 : vpp must not straddle dnwell") -vpp.and(poly.or(li).or(m1).or(m2)).separation(poly.or(li).or(m1).or(m2), 1.5, euclidian).polygons.with_area(2.25,nil).output("vpp.5", "vpp.5 : min. vpp spacing to poly or li or m1 or m2 : 1.5um") -vpp.with_area(0..area(vpp.and(m3))*0.25).output("vpp.5a", "vpp.5a : max. m3 density in vpp : 0.25") -vpp.with_area(0..area(vpp.and(m4))*0.3).output("vpp.5b", "vpp.5b : max. m4 density in vpp : 0.3") -vpp.with_area(0..area(vpp.and(m5))*0.4).output("vpp.5c", "vpp.5c : max. m5 density in vpp : 0.4") -nwell.enclosing(vpp, 1.5, euclidian).output("vpp.8", "vpp.8 : nwell enclosure of vpp : 1.5") -vpp.separation(nwell, 1.5, euclidian).polygons.without_area(0).output("vpp.9", "vpp.9 : vpp spacing to nwell : 1.5") -# rule vpp.10 not coded -# rule vpp.11 not coded because moscap is not defined properly by any gds layer -# rules vpp.12a, 12b, 12c not coded because specific to one cell -if backend_flow = CU - m1.separation(vpp.and(m1), 0.16, euclidian).polygons.without_area(0).output("vpp.13", "vpp.13 : m1 spacing to m1inside vpp : 0.16") -end - -# CAPM -capm.width(1.0, euclidian).output("capm.1", "capm.1 : min. capm width : 1.0um") -capm.isolated(0.84, euclidian).output("capm.2a", "capm.2a : min. capm spacing : 0.84um") -m2.interacting(capm).isolated(1.2, euclidian).output("capm.2b", "capm.2b : min. capm spacing : 1.2um") -m2.enclosing(capm, 0.14, euclidian).output("capm.3", "capm.3 : min. m2 enclosure of capm : 0.14um") -capm.enclosing(via2, 0.14, euclidian).output("capm.4", "capm.4 : min. capm enclosure of via2 : 0.14um") -capm.separation(via2, 0.14, euclidian).output("capm.5", "capm.5 : min. capm spacing to via2 : 0.14um") -capm.sized(-20.0).sized(20.0).output("capm.6", "capm.6 : max. capm lenght/width : 20um") -capm.with_angle(0 .. 90).output("capm.7", "capm.7 : capm not rectangle") -capm.separation(via, 0.14, euclidian).polygons.without_area(0).output("capm.8", "capm.8 : min. capm spacing to via : 0.14um") -capm.and(nwell).edges.not(capm.edges).output("capm.10", "capm.10 : capm must not straddle nwell") -capm.and(diff).edges.not(capm.edges).output("capm.10", "capm.10 : capm must not straddle diff") -capm.and(tap).edges.not(capm.edges).output("capm.10", "capm.10 : capm must not straddle tap") -capm.and(poly).edges.not(capm.edges).output("capm.10", "capm.10 : capm must not straddle poly") -capm.and(li).edges.not(capm.edges).output("capm.10", "capm.10 : capm must not straddle li") -capm.and(m1).edges.not(capm.edges).output("capm.10", "capm.10 : capm must not straddle m1") -capm.separation(m2.not_interacting(capm), 0.14, euclidian).output("capm.11", "capm.11 : min. capm spacing to m2 not overlapping capm : 0.5um") - -end #FEOL - -if BEOL -info("BEOL section") - -# li -not_in_cell5 = source.select("-s8rf2_xcmvpp_hd5_*") -not_in_cell5_li = not_in_cell5.polygons(li_wildcard) -not_in_cell5_li.width(0.17, euclidian).output("li.1", "li.1 : min. li width : 0.17um") - -in_cell5_li = li - not_in_cell5_li -in_cell5_li.width(0.14, euclidian).output("li.1a", "li.1a : min. li width for the cells s8rf2_xcmvpp_hd5_* : 0.14um") - -# rule li.2 not coded - -not_in_cell5_li.space(0.17, euclidian).output("li.3", "li.3 : min. li spacing : 0.17um") -in_cell5_li.space(0.14, euclidian).output("li.3a", "li.3a : min. li spacing for the cells s8rf2_xcmvpp_hd5_* : 0.14um") -licon08 = licon.interacting(li.enclosing(licon, 0.08, euclidian).polygons) -licon_edges_with_less_enclosure_li = li.enclosing(licon, 0.08, projection).second_edges -opposite3 = (licon.edges - licon_edges_with_less_enclosure_li).width(0.17 + 1.dbu, projection).polygons -licon08.not_interacting(opposite3).output("li.5", "li.5 : min. li enclosure of licon of 2 opposite edges : 0.08um") -li.with_area(0..0.0561).output("li.6", "li.6 : min. li area : 0.0561um²") - -# ct -mcon.edges.without_length(0.17).output("ct.1", "ct.1 : minimum/maximum width of mcon : 0.17um") -mcon.space(0.19, euclidian).output("ct.2", "ct.2 : min. mcon spacing : 0.19um") -# rule ct.3 not coded -mcon.not(li).output("ct.4", "ct.4 : mcon should covered by li") -if backend_flow = CU - li.interacting(li.and(m1).not(mcon).with_holes(1,10)).enclosing(mcon, 0.2, euclidian).output("ct.irdrop.1", "ct.irdrop.1 : min. li enclsoure of 1..10 mcon : 0.2um") - li.interacting(li.and(m1).not(mcon).with_holes(11,100)).enclosing(mcon, 0.3, euclidian).output("ct.irdrop.2", "ct.irdrop.2 : min. li enclsoure of 11..100 mcon : 0.3um") -end - -# m1 -m1.width(0.14, euclidian).output("m1.1", "m1.1 : min. m1 width : 0.14um") - -huge_m1 = m1.sized(-1.5).sized(1.5) -non_huge_m1 = m1 - huge_m1 - -non_huge_m1.space(0.14, euclidian).output("m1.2", "m1.2 : min. m1 spacing : 0.14um") - -(huge_m1.separation(non_huge_m1, 0.28, euclidian) + huge_m1.space(0.28, euclidian)).output("m1.3ab", "m1.3ab : min. 3um.m1 spacing m1 : 0.28um") - -not_in_cell6 = layout(source.cell_obj).select("-s8cell_ee_plus_sseln_a", "-s8cell_ee_plus_sseln_b", "-s8cell_ee_plus_sselp_a", "-s8cell_ee_plus_sselp_b", "-s8fpls_pl8", "-s8fs_cmux4_fm") -not_in_cell6_m1 = not_in_cell6.input(m1_wildcard) -not_in_cell6_m1.enclosing(mcon, 0.03, euclidian).output("m1.4", "m1.4 : min. m1 enclosure of mcon : 0.03um") -in_cell6 = layout(source.cell_obj).select("+s8cell_ee_plus_sseln_a", "+s8cell_ee_plus_sseln_b", "+s8cell_ee_plus_sselp_a", "+s8cell_ee_plus_sselp_b", "+s8fpls_pl8", "+s8fs_cmux4_fm") -in_cell6_m1 = in_cell6.input(m1_wildcard) -in_cell6_m1.enclosing(mcon, 0.005, euclidian).output("m1.4a", "m1.4a : min. m1 enclosure of mcon for specific cells : 0.005um") -m1.with_area(0..0.083).output("m1.6", "m1.6 : min. m1 area : 0.083um²") -m1.holes.with_area(0..0.14).output("m1.7", "m1.7 : min. m1 holes area : 0.14um²") -if backend_flow = AL - mcon06 = mcon.interacting(poly.enclosing(m1, 0.06, euclidian).polygons) - mcon_edges_with_less_enclosure_m1 = m1.enclosing(mcon, 0.06, projection).second_edges - opposite4 = (mcon.edges - mcon_edges_with_less_enclosure_m1).width(0.17 + 1.dbu, projection).polygons - mcon06.not_interacting(opposite4).output("m1.5", "m1.5 : min. m1 enclosure of mcon of 2 opposite edges : 0.06um") - # rule m1.pd.1, rule m1.pd.2a, rule m1.pd.2b not coded -end -if bakend_flow = CU - m1.sized(-2.0).sized(2.0).output("m1.11", "m1.11 : max. m1 width after slotting : 4.0um") - # rule m1.12 not coded because inconsistent with m1.11 - # rule m1.13, m1.14, m1.14a not coded : see : https://www.klayout.de/forum/discussion/comment/6759 -end - -# via -#rule via.3 not coded -via.not(m1).output("via.4c.5c", "via.4c.5c : m1 must enclose all via") -if backend_flow = AL - via.not(areaid_mt).edges.without_length(0.15).output("via.1a", "via.1a : minimum/maximum width of via : 0.15um") - via.and(areaid_mt).not_interacting((via.and(areaid_mt).edges.without_length(0.15)).or(via.and(areaid_mt).edges.without_length(0.23)).or(via.and(areaid_mt).edges.without_length(0.28))).output("via.1b", "via.1b : minimum/maximum width of via in areaid.mt: 0.15um or 0.23um or 0.28um") - via.isolated(0.17, euclidian).output("via.2", "via.2 : min. via spacing : 0.17um") - m1.enclosing(via.not_interacting(via.edges.without_length(0.15)), 0.055, euclidian).output("via.4a", "via.4a : min. m1 enclosure of 0.15um via : 0.055um") - m1.enclosing(via.not_interacting(via.edges.without_length(0.23)), 0.03, euclidian).output("via.4b", "via.4b : min. m1 enclosure of 0.23um via : 0.03um") - via1_edges_with_less_enclosure_m1 = m1.enclosing(via.not_interacting(via.edges.without_length(0.15)), 0.085, projection).second_edges - opposite5 = (via.not_interacting(via.edges.without_length(0.15)).edges - via1_edges_with_less_enclosure_m1).width(0.15 + 1.dbu, projection).polygons - via.not_interacting(via.edges.without_length(0.15)).not_interacting(opposite5).output("via1.5a", "via1.5a : min. m1 enclosure of 0.15um via of 2 opposite edges : 0.085um") - via2_edges_with_less_enclosure_m1 = m1.enclosing(via.not_interacting(via.edges.without_length(0.23)), 0.06, projection).second_edges - opposite6 = (via.not_interacting(via.edges.without_length(0.23)).edges - via2_edges_with_less_enclosure_m1).width(0.23 + 1.dbu, projection).polygons - via.not_interacting(via.edges.without_length(0.23)).not_interacting(opposite6).output("via1.5b", "via1.5b : min. m1 enclosure of 0.23um via of 2 opposite edges : 0.06um") -end -if backend_flow = CU - via.not(areaid_mt).edges.without_length(0.18).output("via.11", "via.11 : minimum/maximum width of via : 0.18um") - via.isolated(0.13, euclidian).output("via.12", "via.12 : min. via spacing : 0.13um") - # rule via.13 not coded because not understandable - via1_edges_with_less_enclosure_m1 = m1.enclosing(via, 0.04, projection).second_edges - opposite5 = (via.edges - via1_edges_with_less_enclosure_m1).width(0.18 + 1.dbu, projection).polygons - via.not_interacting(opposite5).output("via1.14", "via1.14 : min. m1 enclosure of 0.04um via of 2 opposite edges : 0.04um") - # rules via.irdrop.1, via.irdrop.2, via.irdrop.3, via.irdrop.4 not coded because not understandable -end - -# m2 -m2.width(0.14, euclidian).output("m2.1", "m2.1 : min. m2 width : 0.14um") - -huge_m2 = m2.sized(-1.5).sized(1.5) -non_huge_m2 = m2 - huge_m2 - -non_huge_m2.space(0.14, euclidian).output("m2.2", "m2.2 : min. m2 spacing : 0.14um") - -(huge_m2.separation(non_huge_m2, 0.28, euclidian) + huge_m2.space(0.28, euclidian)).output("m2.3ab", "m2.3ab : min. 3um.m2 spacing m2 : 0.28um") - -# rule m2.3c not coded -m2.with_area(0..0.0676).output("m2.6", "m2.6 : min. m2 area : 0.0676um²") -m2.holes.with_area(0..0.14).output("m2.7", "m2.7 : min. m2 holes area : 0.14um²") -via.not(m2).output("m2.via", "m2.via : m2 must enclose via") -if backend_flow = AL - m2.enclosing(via, 0.055, euclidian).output("m2.4", "m2.4 : min. m2 enclosure of via : 0.055um") - via_edges_with_less_enclosure_m2 = m2.enclosing(via, 0.085, projection).second_edges - opposite7 = (via.edges - via_edges_with_less_enclosure_m2).width(0.2 + 1.dbu, projection).polygons - via.not_interacting(opposite7).output("m2.5", "m2.5 : min. m2 enclosure of via of 2 opposite edges : 0.085um") - # rule m2.pd.1, rule m2.pd.2a, rule m2.pd.2b not coded -end -if bakend_flow = CU - m2.sized(-2.0).sized(2.0).output("m2.11", "m2.11 : max. m2 width after slotting : 4.0um") - # rule m2.12 not coded because inconsistent with m2.11 - # rule m2.13, m2.14, m2.14a not coded : see : https://www.klayout.de/forum/discussion/comment/6759 -end - -# via2 -#rule via233 not coded -via2.not(m2).output("via2", "via2 : m2 must enclose all via2") -if backend_flow = AL - via2.not(areaid_mt).edges.without_length(0.2).output("via2.1a", "via2.1a : minimum/maximum width of via2 : 0.2um") - via2.and(areaid_mt).not_interacting((via2.and(areaid_mt).edges.without_length(0.2)).or(via2.and(areaid_mt).edges.without_length(1.2)).or(via2.and(areaid_mt).edges.without_length(1.5))).output("via2.1b", "via2.1b : minimum/maximum width of via2 in areaid.mt: 0.2um or 1.2um or 1.5um") - via2.isolated(0.2, euclidian).output("via2.2", "via2.2 : min. via2 spacing : 0.2um") - m2.enclosing(via2, 0.04, euclidian).output("via2.4", "via2.4 : min. m2 enclosure of via2 : 0.04um") - m2.enclosing(via2.not_interacting(via2.edges.without_length(1.5)), 0.14, euclidian).output("via2.4a", "via2.4a : min. m2 enclosure of 1.5um via2 : 0.14um") - via2_edges_with_less_enclosure_m2 = m2.enclosing(via2, 0.085, projection).second_edges - opposite8 = (via2.edges - via2_edges_with_less_enclosure_m2).width(0.2 + 1.dbu, projection).polygons - via2.not_interacting(opposite8).output("via2.5", "via2.5 : min. m2 enclosure of via2 of 2 opposite edges : 0.085um") -end -if backend_flow = CU - via2.edges.without_length(0.21).output("via2.11", "via2.11 : minimum/maximum width of via2 : 0.21um") - via2.isolated(0.18, euclidian).output("via2.12", "via2.12 : min. via2 spacing : 0.18um") - # rule via2.13 not coded because not understandable, or not clear - m2.enclosing(via2, 0.035, euclidian).output("via2.14", "via2.14 : min. m2 enclosure of via2 : 0.035um") - # rules via2.irdrop.1, via2.irdrop.2, via2.irdrop.3, via2.irdrop.4 not coded because not understandable -end - -# m3 -m3.width(0.3, euclidian).output("m3.1", "m3.1 : min. m3 width : 0.3um") - -huge_m3 = m3.sized(-1.5).sized(1.5) -non_huge_m3 = m3 - huge_m3 - -non_huge_m3.space(0.3, euclidian).output("m3.2", "m3.2 : min. m3 spacing : 0.3um") - -(huge_m3.separation(non_huge_m3, 0.4, euclidian) + huge_m3.space(0.4, euclidian)).output("m3.3ab", "m3.3ab : min. 3um.m3 spacing m3 : 0.4um") - -# rule m3.3c not coded -m3.with_area(0..0.24).output("m3.6", "m3.6 : min. m3 area : 0.24um²") -via2.not(m3).output("m3.via2", "m3.via2 : m3 must enclose via2") -if backend_flow = AL - m3.enclosing(via2, 0.065, euclidian).output("m3.4", "m3.4 : min. m3 enclosure of via2 : 0.065um") - via2_edges_with_less_enclosure_m3 = m3.enclosing(via2, 0.085, projection).second_edges - # m3.5 N/A - # opposite9 = (via2.edges - via2_edges_with_less_enclosure_m3).width(0.3 + 1.dbu, projection).polygons - # via2.not_interacting(opposite9).output("m3.5", "m3.5 : min. m3 enclosure of via2 of 2 opposite edges : 0.085um") - # rule m3.pd.1, rule m3.pd.2a, rule m3.pd.2b not coded -end -if bakend_flow = CU - m3.holes.with_area(0..0.2).output("m3.7", "m3.7 : min. m3 holes area : 0.2um²") - m3.sized(-2.0).sized(2.0).output("m3.11", "m3.11 : max. m3 width after slotting : 4.0um") - # rule m3.12 not coded because inconsistent with m3.11 - # rule m3.13, m3.14, m3.14a not coded : see : https://www.klayout.de/forum/discussion/comment/6759 -end - -# via3 -#rule via3.3 not coded -via3.not(m3).output("via3", "via3 : m3 must enclose all via3") -if backend_flow = AL - via3.not(areaid_mt).edges.without_length(0.2).output("via3.1a", "via3.1a : minimum/maximum width of via3 : 0.2um") - via3.and(areaid_mt).not_interacting((via3.and(areaid_mt).edges.without_length(0.2)).or(via3.and(areaid_mt).edges.without_length(0.8))).output("via3.1a", "via3.1a : minimum/maximum width of via3 in areaid.mt: 0.2um or 0.8um") - via3.isolated(0.2, euclidian).output("via3.2", "via3.2 : min. via3 spacing : 0.2um") - m3.enclosing(via3, 0.06, euclidian).output("via3.4", "via3.4 : min. m3 enclosure of via3 : 0.06um") - via3_edges_with_less_enclosure_m3 = m3.enclosing(via3, 0.09, projection).second_edges - opposite10 = (via3.edges - via3_edges_with_less_enclosure_m3).width(0.2 + 1.dbu, projection).polygons - via3.not_interacting(opposite10).output("via3.5", "via3.5 : min. m2 enclosure of via3 of 2 opposite edges : 0.09um") -end -if backend_flow = CU - via3.edges.without_length(0.21).output("via3.11", "via3.11 : minimum/maximum width of via3 : 0.21um") - via3.isolated(0.18, euclidian).output("via3.12", "via3.12 : min. via3 spacing : 0.18um") - m3.enclosing(via3, 0.055, euclidian).output("via3.13", "via3.13 : min. m3 enclosure of via3 : 0.055um") - # rule via3.14 not coded because not understandable, or not clear - # rules via3.irdrop.1, via3.irdrop.2, via3.irdrop.3, via3.irdrop.4 not coded because not understandable -end - -# m4 -m4.width(0.3, euclidian).output("m4.1", "m4.1 : min. m4 width : 0.3um") - -huge_m4 = m4.sized(-1.5).sized(1.5) -non_huge_m4 = m4 - huge_m4 - -non_huge_m4.space(0.3, euclidian).output("m4.2", "m4.2 : min. m4 spacing : 0.3um") - -(huge_m4.separation(non_huge_m4, 0.4, euclidian) + huge_m4.space(0.4, euclidian)).output("m4.5ab", "m4.5ab : min. 3um.m4 spacing m4 : 0.4um") - -m4.with_area(0..0.24).output("m4.4", "m4.4 : min. m4 area : 0.24um²") -via3.not(m4).output("m4.via3", "m4.via3 : m4 must enclose via3") -if backend_flow = AL - m4.enclosing(via3, 0.065, euclidian).output("m4.3", "m4.3 : min. m4 enclosure of via3 : 0.065um") - # m4.5 doesn't exist - # via3_edges_with_less_enclosure_m4 = m4.enclosing(via2, 0.085, projection).second_edges - # opposite9 = (via3.edges - via3_edges_with_less_enclosure_m4).width(0.3 + 1.dbu, projection).polygons - # via3.not_interacting(opposite9).output("m4.5", "m4.5 : min. m4 enclosure of via3 of 2 opposite edges : 0.085um") - # rule m4.pd.1, rule m4.pd.2a, rule m4.pd.2b not coded -end -if bakend_flow = CU - m4.holes.with_area(0..0.2).output("m4.7", "m4.7 : min. m4 holes area : 0.2um²") - m4.sized(-5.0).sized(5.0).output("m4.11", "m4.11 : max. m4 width after slotting : 10.0um") - # rule m4.12 not coded because inconsistent with m4.11 - # rule m4.13, m4.14, m4.14a not coded : see : https://www.klayout.de/forum/discussion/comment/6759 - m4.enclosing(via3, 0.06, euclidian).output("m4.15", "m4.15 : min. m4 enclosure of via3 : 0.06um") -end - -# via4 -via4.edges.without_length(0.8).output("via4.1a", "via4.1a : minimum/maximum width of via4 : 0.8um") -via4.isolated(0.8, euclidian).output("via4.2", "via4.2 : min. via4 spacing : 0.8um") -#rule via4.3 not coded -m4.enclosing(via4, 0.19, euclidian).output("via4.4", "via4.4 : min. m4 enclosure of via4 : 0.19um") -via4.not(m4).output("via4", "via4 : m4 must enclose all via4") -if backend_flow = CU - # rules via4.irdrop.1, via4.irdrop.2, via4.irdrop.3, via4.irdrop.4 not coded because not understandable -end - -# m5 -m5.width(1.6, euclidian).output("m5.1", "m5.1 : min. m5 width : 1.6um") - -m5.space(1.6, euclidian).output("m5.2", "m5.2 : min. m5 spacing : 1.6um") - -via4.not(m5).output("m5.via4", "m5.via4 : m5 must enclose via4") -m5.enclosing(via4, 0.31, euclidian).output("m5.3", "m4.3 : min. m5 enclosure of via4 : 0.31um") - -# nsm -nsm.width(3.0, euclidian).output("nsm.1", "nsm.1 : min. nsm width : 3.0um") -nsm.isolated(4.0, euclidian).output("nsm.2", "nsm.2 : min. nsm spacing : 4.0um") -nsm.enclosing(diff, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of diff : 3.0um") -nsm.enclosing(tap, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of tap : 3.0um") -nsm.enclosing(poly, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of poly : 3.0um") -nsm.enclosing(li, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of li : 3.0um") -nsm.enclosing(m1, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of m1 : 3.0um") -nsm.enclosing(m2, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of m2 : 3.0um") -nsm.enclosing(m3, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of m3 : 3.0um") -nsm.enclosing(m4, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of m4 : 3.0um") -nsm.enclosing(m5, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of m5 : 3.0um") -nsm.enclosing(cfom, 3.0, euclidian).output("nsm.4", "nsm.4 : min. nsm enclosure of cfom : 3.0um") -if backend_flow = AL - nsm.separation(diff, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to diff : 1.0um") - nsm.separation(tap, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to tap : 1.0um") - nsm.separation(poly, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to poly : 1.0um") - nsm.separation(li, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to li : 1.0um") - nsm.separation(m1, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to m1 : 1.0um") - nsm.separation(m2, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to m2 : 1.0um") - nsm.separation(m3, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to m3 : 1.0um") - nsm.separation(m4, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to m4 : 1.0um") - nsm.separation(m5, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to m5 : 1.0um") - nsm.separation(cfom, 1.0, euclidian).output("nsm.3", "nsm.3 : min. nsm spacing to cfom : 1.0um") -end - -# pad -pad.isolated(1.27, euclidian).output("pad.2", "pad.2 : min. pad spacing : 1.27um") - -end #BEOL - -if FEOL -info("FEOL section") - -# mf -mf.not_interacting(mf.edges.without_length(0.8)).output("mf.1", "mf.1 : minimum/maximum width of fuse : 0.8um") -mf.not_interacting(mf.edges.without_length(7.2)).output("mf.2", "mf.2 : minimum/maximum length of fuse : 7.2um") -mf.isolated(1.96, euclidian).output("mf.3", "mf.3 : min. fuse center spacing : 2.76um") -# fuses need more clarification on fuse_shield, fuse layers ... - -# hvi -hvi.width(0.6, euclidian).output("hvi.1", "hvi.1 : min. hvi width : 0.6um") -hvi.isolated(0.7, euclidian).output("hvi.2", "hvi.2 : min. hvi spacing, merge if less : 0.7um") -hvi.and(tunm).output("hvi.4", "hvi.4 : hvi must not overlapp tunm") -hvi.and(nwell).separation(nwell, 2.0, euclidian).output("hvnwell.8", "hvnwelli.8 : min. hvnwel spacing to nwell : 2.0") -areaid_hl.not(hvi).output("hvnwel.9", "hvnwell.9 : hvi must overlapp hvnwell") -# rule hvnell.10 not coded -diff.not(psdm.and(diff_rs)).and(hvi).width(0.29, euclidian).output("hvdifftap.14", "hvdifftap.14 : min. diff inside hvi width : 0.29um") -diff.and(psdm.and(diff_rs)).and(hvi).width(0.15, euclidian).output("hvdifftap.14a", "hvdifftap.14a : min. p+diff resistor inside hvi width : 0.15um") -diff.and(hvi).isolated(0.3, euclidian).output("hvdifftap.15a", "hvdifftap.15a : min. diff inside hvi spacing : 0.3um") -diff.and(hvi).and(nsdm).separation(diff.and(hvi).and(psdm), 0.37, euclidian).polygons.without_area(0).output("hvdifftap.15b", "hvdifftap.15b : min. n+diff inside hvi spacing to p+diff inside hvi except abutting: 0.37um") -tap.and(hvi).edges.and(diff).without_length(0.7).output("hvdifftap.16", "hvdifftap.16 : min. tap inside hvi abuttng diff : 0.7um") -hvi.and(nwell).enclosing(diff, 0.33, euclidian).output("hvdifftap.17", "hvdifftap.17 : min. hvnwell enclosure of p+diff : 0.33um") -hvi.and(nwell).separation(diff, 0.43, euclidian).output("hvdifftap.18", "hvdifftap.18 : min. hvnwell spacing to n+diff : 0.43um") -hvi.and(nwell).enclosing(tap, 0.33, euclidian).output("hvdifftap.19", "hvdifftap.19 : min. hvnwell enclosure of n+tap : 0.33um") -hvi.and(nwell).separation(tap, 0.43, euclidian).output("hvdifftap.20", "hvdifftap.20 : min. hvnwell spacing to p+tap : 0.43um") -hvi.and(diff).edges.not(diff.edges).output("hvdifftap.21", "hvdifftap.21 : diff must not straddle hvi") -hvi.and(tap).edges.not(tap.edges).output("hvdifftap.21", "hvdifftap.21 : tap must not straddle hvi") -hvi.enclosing(difftap, 0.18, euclidian).output("hvdifftap.22", "hvdifftap.22 : min. hvi enclosure of diff or tap : 0.18um") -hvi.separation(difftap, 0.18, euclidian).output("hvdifftap.23", "hvdifftap.23 : min. hvi spacing to diff or tap : 0.18um") -hvi.and(diff).not(nwell).separation(nwell, 0.43, euclidian).output("hvdifftap.24", "hvdifftap.24 : min. hv n+diff spacing to nwell : 0.43um") -diff.and(hvi).not(nwell).isolated(1.07, euclidian).polygons.and(tap).output("hvdifftap.25", "hvdifftap.25 : min. n+diff inside hvi spacing accros p+tap : 1.07um") -diff.not(poly).edges.and(gate.and(hvi).edges).space(0.35, euclidian).output("hvpoly.13", "hvpoly.13: min. hvi gate length : 0.5um") -hvi.and(poly).edges.not(poly.edges).output("hvpoly.14", "hvpoly.14 : poly must not straddle hvi") - -# hvntm -hvntm.width(0.7, euclidian).output("hvntm.1", "hvntm.1 : min. hvntm width : 0.7um") -hvntm.isolated(0.7, euclidian).output("hvntm.2", "hvntm.2 : min. hvntm spacing : 0.7um") -hvntm.enclosing(diff.and(nwell).and(hvi), 0.185, euclidian).output("hvntm.3", "hvntm.3 : min. hvntm enclosure of hv n+diff : 0.185um") -hvntm.separation(diff.not(nwell).not(hvi), 0.185, euclidian).output("hvntm.4", "hvntm.4 : min. hvntm spacing to n+diff : 0.185um") -hvntm.separation(diff.and(nwell).not(hvi), 0.185, euclidian).output("hvntm.5", "hvntm.5 : min. hvntm spacing to p+diff : 0.185um") -hvntm.separation(tap.not(nwell).not(hvi), 0.185, euclidian).polygons.without_area(0).output("hvntm.6a", "hvntm.6a : min. hvntm spacing to p+tap : 0.185um") -hvntm.and(areaid_ce).output("hvntm.9", "hvntm.9 : hvntm must not overlapp areaid.ce") - -# denmos -poly.not_interacting(pwde).interacting(areaid_en).width(1.055, projection).output("denmos.1", "denmos.1 : min. de_nfet gate width : 1.055um") -diff.not_interacting(pwde).enclosing(poly.interacting(areaid_en), 0.28, projection).polygons.without_area(0).output("denmos.2", "denmos.2 : min. de_nfet source ouside poly width : 0.28um") -diff.not_interacting(pwde).and(poly.interacting(areaid_en)).width(0.925, projection).output("denmos.3", "denmos.3 : min. de_nfet source inside poly width : 0.925um") -diff.not_interacting(pwde).interacting(areaid_en).not_interacting(poly).width(0.17, euclidian).output("denmos.4", "denmos.4 : min. de_nfet drain width : 0.17um") -nwell.not_interacting(pwde).and(poly.interacting(areaid_en)).width(0.225, projection).polygons.or(nwell.and(poly.interacting(areaid_en)).sized(-0.1125).sized(0.1125)).output("denmos.5", "denmos.5 : min. de_nfet source inside nwell width : 0.225m") -diff.not_interacting(pwde).interacting(areaid_en).not_interacting(poly).separation(diff.interacting(poly.interacting(areaid_en)), 1.585, projection).output("denmos.6", "denmos.6 : min. de_nfet source spacing to drain : 1.585um") -nwell.not_interacting(pwde).and(poly.and(diff).interacting(areaid_en)).edges.without_length(5.0, nil).output("denmos.7", "denmos.7 : min. de_nfet channel width : 5.0um") -diff.not_interacting(pwde).interacting(areaid_en).not_interacting(poly).edges.without_angle(45).without_angle(135).without_angle(225).without_angle(315).output("denmos.8", "denmos.8 : 90deg. not allowed for de_nfet drain") -nwell.not_interacting(pwde).interacting(areaid_en).edges.without_angle(45).without_angle(135).without_angle(225).without_angle(315).output("denmos.9a", "denmos.9a : 90deg. not allowed for de_nfet nwell") -nwell.not_interacting(pwde).interacting(areaid_en).edges.with_angle(45).without_length(0.607..0.609).output("denmos.9a", "denmos.9a : 45deg. bevels of de_nfet nwell should be 0.43um from corners") -nwell.not_interacting(pwde).interacting(areaid_en).edges.with_angle(135).without_length(0.607..0.609).output("denmos.9a", "denmos.9a : 45deg. bevels of de_nfet nwell should be 0.43um from corners") -diff.not_interacting(pwde).interacting(areaid_en).not_interacting(poly).edges.with_angle(45).without_length(0.7..0.71).output("denmos.9b", "denmos.9b : 45deg. bevels of de_nfet drain should be 0.05um from corners") -diff.not_interacting(pwde).interacting(areaid_en).not_interacting(poly).edges.with_angle(135).without_length(0.7..0.71).output("denmos.9b", "denmos.9b : 45deg. bevels of de_nfet drain should be 0.05um from corners") -nwell.not_interacting(pwde).enclosing(diff.interacting(areaid_en).not_interacting(poly), 0.66, euclidian).output("denmos.10", "denmos.10 : min. nwell enclosure of de_nfet drain : 0.66um") -nwell.not_interacting(pwde).interacting(areaid_en).separation(tap.not(nwell), 0.86, euclidian).output("denmos.11", "denmos.11 : min. de_nfet nwell spacing to tap : 0.86um") -nwell.not_interacting(pwde).interacting(areaid_en).isolated(2.4, euclidian).output("denmos.12", "denmos.12 : min. de_nfet nwell : 2.4um") -nsdm.not_interacting(pwde).enclosing(diff.interacting(areaid_en).interacting(poly), 0.13, euclidian).output("denmos.13", "denmos.13 : min. nsdm enclosure of de_nfet source : 0.13um") - -# depmos -poly.interacting(pwde).interacting(areaid_en).width(1.05, projection).output("depmos.1", "depmos.1 : min. de_pfet gate width : 1.05um") -diff.interacting(pwde).enclosing(poly.interacting(areaid_en), 0.28, projection).polygons.without_area(0).output("depmos.2", "depmos.2 : min. de_pfet source ouside poly width : 0.28um") -diff.interacting(pwde).and(poly.interacting(areaid_en)).width(0.92, projection).output("depmos.3", "depmos.3 : min. de_pfet source inside poly width : 0.92um") -diff.interacting(pwde).interacting(areaid_en).not_interacting(poly).width(0.17, euclidian).output("depmos.4", "depmos.4 : min. de_pfet drain width : 0.17um") -pwde.not(nwell).and(poly.interacting(areaid_en)).width(0.26, projection).polygons.or(pwde.not(nwell).and(poly.interacting(areaid_en)).sized(-0.13).sized(0.13)).output("depmos.5", "depmos.5 : min. de_pfet source inside nwell width : 0.26m") -diff.interacting(pwde).interacting(areaid_en).not_interacting(poly).separation(diff.interacting(poly.interacting(areaid_en)), 1.19, projection).output("depmos.6", "depmos.6 : min. de_pfet source spacing to drain : 1.19um") -nwell.interacting(pwde).and(poly.and(diff).interacting(areaid_en)).edges.without_length(5.0, nil).output("depmos.7", "depmos.7 : min. de_pfet channel width : 5.0um") -diff.interacting(pwde).interacting(areaid_en).not_interacting(poly).edges.without_angle(45).without_angle(135).without_angle(225).without_angle(315).output("depmos.8", "depmos.8 : 90deg. not allowed for de_pfet drain") -pwde.not(nwell).interacting(areaid_en).edges.without_angle(45).without_angle(135).without_angle(225).without_angle(315).output("depmos.9a", "depmos.9a : 90deg. not allowed for de_pfet pwell") -pwde.not(nwell).interacting(areaid_en).edges.with_angle(45).without_length(0.607..0.609).output("depmos.9a", "depmos.9a : 45deg. bevels of de_pfet pwell should be 0.43um from corners") -pwde.not(nwell).interacting(areaid_en).edges.with_angle(135).without_length(0.607..0.609).output("depmos.9a", "depmos.9a : 45deg. bevels of de_pfet pwell should be 0.43um from corners") -diff.interacting(pwde).interacting(areaid_en).not_interacting(poly).edges.with_angle(45).without_length(0.7..0.71).output("depmos.9b", "depmos.9b : 45deg. bevels of de_pfet drain should be 0.05um from corners") -diff.interacting(pwde).interacting(areaid_en).not_interacting(poly).edges.with_angle(135).without_length(0.7..0.71).output("depmos.9b", "depmos.9b : 45deg. bevels of de_pfet drain should be 0.05um from corners") -nwell.interacting(pwde).separation(diff.interacting(areaid_en).not_interacting(poly), 0.86, euclidian).output("depmos.10", "depmos.10 : min. pwell enclosure of de_pfet drain : 0.86um") -pwde.not(nwell).interacting(areaid_en).separation(tap.and(nwell), 0.66, euclidian).output("depmos.11", "depmos.11 : min. de_pfet pwell spacing to tap : 0.66um") -psdm.interacting(pwde).enclosing(diff.interacting(areaid_en).interacting(poly), 0.13, euclidian).output("depmos.12", "depmos.12 : min. psdm enclosure of de_pfet source : 0.13um") - -# extd -areaid_en.and(difftap).edges.not(difftap.edges).output("extd.1", "extd.1 : difftap must not straddle areaid.en") -difftap.interacting(areaid_en).not(poly).with_area(0).output("extd.2", "extd.2 : poly must not overlapp entirely difftap in areaid.en") -# rules extd.4, extd.5, extd.6, extd.7 not coded because specific to some cells - -# vhvi -# rules vhvi.vhv.1, vhvi.vhv.2, vhvi.vhv.3, vhvi.vhv.4, vhvi.vhv.5, vhvi.vhv.6 not coded -vhvi.width(0.02, euclidian).output("vhvi.1", "vhvi.1 : min. vhvi width : 0.02um") -vhvi.and(areaid_ce).output("vhvi.2", "vhvi.2 : vhvi must not overlap areaid.ce") -vhvi.and(hvi).output("vhvi.3", "vhvi.3 : vhvi must not overlap hvi") -# rules vhvi.4, vhvi.6 not coded -vhvi.and(diff).edges.not(diff.edges).output("vhvi.5", "vhvi.5 : vhvi must not straddle diff") -vhvi.and(tap).edges.not(tap.edges).output("vhvi.5", "vhvi.5 : vhvi must not straddle tap") -vhvi.and(poly).edges.not(poly.edges).output("vhvi.7", "vhvi.7 : vhvi must not straddle poly") - -nwell.and(vhvi).separation(nwell, 2.5, euclidian).output("hv.nwell.1", "hv.nwell.1 : min. vhvi nwell spacing to nwell : 2.5um") -diff.and(vhvi).isolated(0.3, euclidian).output("hv.diff.1", "hv.diff.1 : min. vhvi diff spacing : 0.3um") -nwell.interacting(diff.and(vhvi)).separation(diff.not(nwell), 0.43, euclidian).output("hv.diff.2", "hv.diff.2 : min. vhvi nwell spacing n+diff : 0.43um") -diff.and(vhvi).not(nwell).separation(nwell, 0.55, euclidian).output("hv.diff.3a", "hv.diff.3a : min. vhvi n+diff spacing nwell : 0.55um") -# rule hv.diff.3b not coded -poly.and(vhvi).not(diff).separation(diff, 0.3, euclidian).polygons.without_area(0).output("hv.poly.2", "hv.poly.2 : min. vhvi poly spacing to diff : 0.3um") -poly.and(vhvi).not(diff).separation(nwell, 0.55, euclidian).polygons.without_area(0).output("hv.poly.3", "hv.poly.3 : min. vhvi poly spacing to nwell : 0.55um") -nwell.enclosing(poly.and(vhvi).not(diff), 0.3, euclidian).polygons.without_area(0).output("hv.poly.4", "hv.poly.4 : min. nwell enclosure of vhvi poly : 0.3um") -#poly.and(vhvi).enclosing(diff.interacting(areaid_en), 0.16, projection).polygons.without_area(0).output("hv.poly.6", "hv.poly.6 : min. poly enclosure of hvfet gate : 0.16um") -# rule hv.poly.7 not coded - -# uhvi -uhvi.and(diff).edges.not(diff.edges).output("uhvi.1", "uhvi.1 : diff must not straddle uhvi") -uhvi.and(tap).edges.not(tap.edges).output("uhvi.1", "uhvi.1 : tap must not straddle uhvi") -uhvi.and(poly).edges.not(poly.edges).output("uhvi.2", "uhvi.2 : poly must not straddle uhvi") -pwbm.not(uhvi).output("uhvi.3", "uhvi.3 : uhvi must not enclose pwbm") -uhvi.and(dnwell).edges.not(dnwell.edges).output("uhvi.4", "uhvi.4 : dnwell must not straddle uhvi") -areaid_en20.not(uhvi).output("uhvi.5", "uhvi.5 : uhvi must not enclose areaid.en20") -#dnwell.not(uhvi).output("uhvi.6", "uhvi.6 : uhvi must not enclose dnwell") -natfet.not(uhvi).output("uhvi.7", "uhvi.7 : uhvi must not enclose natfet") - -# pwell_res -pwell_rs.width(2.65).output("pwres.2", "pwres.2 : min. pwell resistor width : 2.65um") -pwell_rs.sized(-2.65).sized(2.65).output("pwres.2", "pwres.2 : max. pwell resistor width : 2.65um") -pwell_rs.interacting(pwell_rs.edges.with_length(2.651,26.499)).output("pwres.3", "pwres.3 : min. pwell resistor length : 26.5um") -pwell_rs.interacting(pwell_rs.edges.with_length(265.0, nil)).output("pwres.4", "pwres.4 : max. pwell resistor length : 265um") -tap.interacting(pwell_rs).separation(nwell, 0.22, euclidian).output("pwres.5", "pwres.5 : min. pwell resistor tap spacing to nwell : 0.22um") -tap.interacting(pwell_rs).and(tap.sized(0.22).and(nwell)).output("pwres.5", "pwres.5 : max. pwell resistor tap spacing to nwell : 0.22um") -tap.interacting(pwell_rs).width(0.53).output("pwres.6", "pwres.6 : min. width of tap inside pwell resistor : 0.53um") -tap.interacting(pwell_rs).sized(-0.265).sized(0.265).output("pwres.6", "pwres.6 : max. width of tap inside pwell resistor : 0.53um") -# rules pwres.7a, pwres.7b not coded -pwell_rs.and(diff).output("pwres.8", "pwres.8 : diff not allowed inside pwell resistor") -pwell_rs.and(poly).output("pwres.8", "pwres.8 : poly not allowed inside pwell resistor") -# rules pwres.9, pwres.10 not coded - -# rf_diode -areaid_re.with_angle(0 .. 90).output("rfdiode.1", "rfdiode.1 : non 90 degree angle areaid.re") -areaid_re.not(nwell).or(nwell.interacting(areaid_re).not(areaid_re)).output("rfdiode.2", "rfdiode.2 : areaid.re must coincide rf nwell diode") -# rule rfdiode.3 not coded - -end #FEOL - -if OFFGRID -info("OFFGRID-ANGLES section") - -dnwell.ongrid(0.005).output("dnwell_OFFGRID", "x.1b : OFFGRID vertex on dnwell") -dnwell.with_angle(0 .. 45).output("dnwell_angle", "x.3a : non 45 degree angle dnwell") -nwell.ongrid(0.005).output("nwell_OFFGRID", "x.1b : OFFGRID vertex on nwell") -nwell.with_angle(0 .. 45).output("nwell_angle", "x.3a : non 45 degree angle nwell") -pwbm.ongrid(0.005).output("pwbm_OFFGRID", "x.1b : OFFGRID vertex on pwbm") -pwbm.with_angle(0 .. 45).output("pwbm_angle", "x.3a : non 45 degree angle pwbm") -pwde.ongrid(0.005).output("pwde_OFFGRID", "x.1b : OFFGRID vertex on pwde") -pwde.with_angle(0 .. 45).output("pwde_angle", "x.3a : non 45 degree angle pwde") -hvtp.ongrid(0.005).output("hvtp_OFFGRID", "x.1b : OFFGRID vertex on hvtp") -hvtp.with_angle(0 .. 45).output("hvtp_angle", "x.3a : non 45 degree angle hvtp") -hvtr.ongrid(0.005).output("hvtr_OFFGRID", "x.1b : OFFGRID vertex on hvtr") -hvtr.with_angle(0 .. 45).output("hvtr_angle", "x.3a : non 45 degree angle hvtr") -lvtn.ongrid(0.005).output("lvtn_OFFGRID", "x.1b : OFFGRID vertex on lvtn") -lvtn.with_angle(0 .. 45).output("lvtn_angle", "x.3a : non 45 degree angle lvtn") -ncm.ongrid(0.005).output("ncm_OFFGRID", "x.1b : OFFGRID vertex on ncm") -ncm.with_angle(0 .. 45).output("ncm_angle", "x.3a : non 45 degree angle ncm") -diff.ongrid(0.005).output("diff_OFFGRID", "x.1b : OFFGRID vertex on diff") -tap.ongrid(0.005).output("tap_OFFGRID", "x.1b : OFFGRID vertex on tap") -diff.not(areaid_en.and(uhvi)).with_angle(0 .. 90).output("diff_angle", "x.2 : non 90 degree angle diff") -diff.and(areaid_en.and(uhvi)).with_angle(0 .. 45).output("diff_angle", "x.2c : non 45 degree angle diff") -tap.not(areaid_en.and(uhvi)).with_angle(0 .. 90).output("tap_angle", "x.2 : non 90 degree angle tap") -tap.and(areaid_en.and(uhvi)).with_angle(0 .. 45).output("tap_angle", "x.2c : non 45 degree angle tap") -tunm.ongrid(0.005).output("tunm_OFFGRID", "x.1b : OFFGRID vertex on tunm") -tunm.with_angle(0 .. 45).output("tunm_angle", "x.3a : non 45 degree angle tunm") -poly.ongrid(0.005).output("poly_OFFGRID", "x.1b : OFFGRID vertex on poly") -poly.with_angle(0 .. 90).output("poly_angle", "x.2 : non 90 degree angle poly") -rpm.ongrid(0.005).output("rpm_OFFGRID", "x.1b : OFFGRID vertex on rpm") -rpm.with_angle(0 .. 45).output("rpm_angle", "x.3a : non 45 degree angle rpm") -npc.ongrid(0.005).output("npc_OFFGRID", "x.1b : OFFGRID vertex on npc") -npc.with_angle(0 .. 45).output("npc_angle", "x.3a : non 45 degree angle npc") -nsdm.ongrid(0.005).output("nsdm_OFFGRID", "x.1b : OFFGRID vertex on nsdm") -nsdm.with_angle(0 .. 45).output("nsdm_angle", "x.3a : non 45 degree angle nsdm") -psdm.ongrid(0.005).output("psdm_OFFGRID", "x.1b : OFFGRID vertex on psdm") -psdm.with_angle(0 .. 45).output("psdm_angle", "x.3a : non 45 degree angle psdm") -licon.ongrid(0.005).output("licon_OFFGRID", "x.1b : OFFGRID vertex on licon") -licon.with_angle(0 .. 90).output("licon_angle", "x.2 : non 90 degree angle licon") -li.ongrid(0.005).output("li_OFFGRID", "x.1b : OFFGRID vertex on li") -li.with_angle(0 .. 45).output("li_angle", "x.3a : non 45 degree angle li") -mcon.ongrid(0.005).output("ct_OFFGRID", "x.1b : OFFGRID vertex on mcon") -mcon.with_angle(0 .. 90).output("ct_angle", "x.2 : non 90 degree angle mcon") -vpp.ongrid(0.005).output("vpp_OFFGRID", "x.1b : OFFGRID vertex on vpp") -vpp.with_angle(0 .. 45).output("vpp_angle", "x.3a : non 45 degree angle vpp") -m1.ongrid(0.005).output("m1_OFFGRID", "x.1b : OFFGRID vertex on m1") -m1.with_angle(0 .. 45).output("m1_angle", "x.3a : non 45 degree angle m1") -via.ongrid(0.005).output("via_OFFGRID", "x.1b : OFFGRID vertex on via") -via.with_angle(0 .. 90).output("via_angle", "x.2 : non 90 degree angle via") -m2.ongrid(0.005).output("m2_OFFGRID", "x.1b : OFFGRID vertex on m2") -m2.with_angle(0 .. 45).output("m2_angle", "x.3a : non 45 degree angle m2") -via2.ongrid(0.005).output("via2_OFFGRID", "x.1b : OFFGRID vertex on via2") -via2.with_angle(0 .. 90).output("via2_angle", "x.2 : non 90 degree angle via2") -m3.ongrid(0.005).output("m3_OFFGRID", "x.1b : OFFGRID vertex on m3") -m3.with_angle(0 .. 45).output("m3_angle", "x.3a : non 45 degree angle m3") -via3.ongrid(0.005).output("via3_OFFGRID", "x.1b : OFFGRID vertex on via3") -via3.with_angle(0 .. 90).output("via3_angle", "x.2 : non 90 degree angle via3") -nsm.ongrid(0.005).output("nsm_OFFGRID", "x.1b : OFFGRID vertex on nsm") -nsm.with_angle(0 .. 45).output("nsm_angle", "x.3a : non 45 degree angle nsm") -m4.ongrid(0.005).output("m4_OFFGRID", "x.1b : OFFGRID vertex on m4") -m4.with_angle(0 .. 45).output("m4_angle", "x.3a : non 45 degree angle m4") -via4.ongrid(0.005).output("via4_OFFGRID", "x.1b : OFFGRID vertex on via4") -via4.with_angle(0 .. 90).output("via4_angle", "x.2 : non 90 degree angle via4") -m5.ongrid(0.005).output("m5_OFFGRID", "x.1b : OFFGRID vertex on m5") -m5.with_angle(0 .. 45).output("m5_angle", "x.3a : non 45 degree angle m5") -pad.ongrid(0.005).output("pad_OFFGRID", "x.1b : OFFGRID vertex on pad") -pad.with_angle(0 .. 45).output("pad_angle", "x.3a : non 45 degree angle pad") -mf.ongrid(0.005).output("mf_OFFGRID", "x.1b : OFFGRID vertex on mf") -mf.with_angle(0 .. 90).output("mf_angle", "x.2 : non 90 degree angle mf") -hvi.ongrid(0.005).output("hvi_OFFGRID", "x.1b : OFFGRID vertex on hvi") -hvi.with_angle(0 .. 45).output("hvi_angle", "x.3a : non 45 degree angle hvi") -hvntm.ongrid(0.005).output("hvntm_OFFGRID", "x.1b : OFFGRID vertex on hvntm") -hvntm.with_angle(0 .. 45).output("hvntm_angle", "x.3a : non 45 degree angle hvntm") -vhvi.ongrid(0.005).output("vhvi_OFFGRID", "x.1b : OFFGRID vertex on vhvi") -vhvi.with_angle(0 .. 45).output("vhvi_angle", "x.3a : non 45 degree angle vhvi") -uhvi.ongrid(0.005).output("uhvi_OFFGRID", "x.1b : OFFGRID vertex on uhvi") -uhvi.with_angle(0 .. 45).output("uhvi_angle", "x.3a : non 45 degree angle uhvi") -pwell_rs.ongrid(0.005).output("pwell_rs_OFFGRID", "x.1b : OFFGRID vertex on pwell_rs") -pwell_rs.with_angle(0 .. 45).output("pwell_rs_angle", "x.3a : non 45 degree angle pwell_rs") -areaid_re.ongrid(0.005).output("areaid_re_OFFGRID", "x.1b : OFFGRID vertex on areaid.re") - -end #OFFGRID - diff --git a/flow/platforms/sky130hd/drc/sky130hd_drc.py b/flow/platforms/sky130hd/drc/sky130hd_drc.py new file mode 100644 index 0000000000..11108d3e89 --- /dev/null +++ b/flow/platforms/sky130hd/drc/sky130hd_drc.py @@ -0,0 +1,1825 @@ +# +# DRC for SKY130 according to : +# https://skywater-pdk.readthedocs.io/en/latest/rules/periphery.html +# https://skywater-pdk.readthedocs.io/en/latest/rules/layers.html +# +# Distributed under GNU GPLv3: https://www.gnu.org/licenses/ +# +# History : +# 2020-10-04 : v1.0 : initial release +# +########################################################################################## +"""SKY130 DRC deck, run by flow/util/klayout_drc.py on the klayout module. + +Translated line by line from KLayout's DRC language. +""" + +import klayout.db as db + +from klayout_drc import Layer, euclidian, projection + +DESCRIPTION = "SKY130 DRC runset" +MODE = "deep" +THREADS = 4 + +# The Al back-end flow; the Cu rules are not run. +CU = False + +# enable / disable rule groups +FEOL = False # front-end-of-line checks +BEOL = True # back-end-of-line checks +OFFGRID = True # manufacturing grid/angle checks + + +def with_holes(layer, lo, hi): + """Polygons with lo to hi holes, both inclusive, hi None for no limit.""" + new_data = db.Region() + for p in layer.data.each(): + if p.holes() >= (lo or 0) and (hi is None or p.holes() <= hi): + new_data.insert(p) + return Layer(layer.deck, new_data) + + +def area(layer): + """The area of a layer in square microns.""" + return layer.data.area() * layer.deck.dbu * layer.deck.dbu + + +def drc(deck): + # layers definitions + ######################## + + # all except purpose (datatype) 5 -- label and 44 -- via + li_wildcard = "67/0-4,6-43,45-*" + mcon_wildcard = "67/44" + + m1_wildcard = "68/0-4,6-43,45-*" + via_wildcard = "68/44" + + m2_wildcard = "69/0-4,6-43,45-*" + via2_wildcard = "69/44" + + m3_wildcard = "70/0-4,6-43,45-*" + via3_wildcard = "70/44" + + m4_wildcard = "71/0-4,6-43,45-*" + via4_wildcard = "71/44" + + m5_wildcard = "72/0-4,6-43,45-*" + + diff = deck.input(65, 20) + tap = deck.polygons(65, 44) + nwell = deck.polygons(64, 20) + dnwell = deck.polygons(64, 18) + pwbm = deck.polygons(19, 44) + pwde = deck.polygons(124, 20) + natfet = deck.polygons(124, 21) + hvtr = deck.polygons(18, 20) + hvtp = deck.polygons(78, 44) + ldntm = deck.polygons(11, 44) + hvi = deck.polygons(75, 20) + tunm = deck.polygons(80, 20) + lvtn = deck.polygons(125, 44) + poly = deck.polygons(66, 20) + hvntm = deck.polygons(125, 20) + nsdm = deck.polygons(93, 44) + psdm = deck.polygons(94, 20) + rpm = deck.polygons(86, 20) + urpm = deck.polygons(79, 20) + npc = deck.polygons(95, 20) + licon = deck.polygons(66, 44) + + li = deck.polygons(li_wildcard) + mcon = deck.polygons(mcon_wildcard) + + m1 = deck.polygons(m1_wildcard) + via = deck.polygons(via_wildcard) + + m2 = deck.polygons(m2_wildcard) + via2 = deck.polygons(via2_wildcard) + + m3 = deck.polygons(m3_wildcard) + via3 = deck.polygons(via3_wildcard) + + m4 = deck.polygons(m4_wildcard) + via4 = deck.polygons(via4_wildcard) + + m5 = deck.polygons(m5_wildcard) + + pad = deck.polygons(76, 20) + nsm = deck.polygons(61, 20) + capm = deck.polygons(89, 44) + cap2m = deck.polygons(97, 44) + vhvi = deck.polygons(74, 21) + uhvi = deck.polygons(74, 22) + npn = deck.polygons(82, 20) + inductor = deck.polygons(82, 24) + vpp = deck.polygons(82, 64) + pnp = deck.polygons(82, 44) + lvs_prune = deck.polygons(84, 44) + ncm = deck.polygons(92, 44) + padcenter = deck.polygons(81, 20) + mf = deck.polygons(76, 44) + areaid_sl = deck.polygons(81, 1) + areaid_ce = deck.polygons(81, 2) + areaid_fe = deck.polygons(81, 3) + areaid_sc = deck.polygons(81, 4) + areaid_sf = deck.polygons(81, 6) + areaid_sw = deck.polygons(81, 7) + areaid_sr = deck.polygons(81, 8) + areaid_mt = deck.polygons(81, 10) + areaid_dt = deck.polygons(81, 11) + areaid_ft = deck.polygons(81, 12) + areaid_ww = deck.polygons(81, 13) + areaid_ld = deck.polygons(81, 14) + areaid_ns = deck.polygons(81, 15) + areaid_ij = deck.polygons(81, 17) + areaid_zr = deck.polygons(81, 18) + areaid_ed = deck.polygons(81, 19) + areaid_de = deck.polygons(81, 23) + areaid_rd = deck.polygons(81, 24) + areaid_dn = deck.polygons(81, 50) + areaid_cr = deck.polygons(81, 51) + areaid_cd = deck.polygons(81, 52) + areaid_st = deck.polygons(81, 53) + areaid_op = deck.polygons(81, 54) + areaid_en = deck.polygons(81, 57) + areaid_en20 = deck.polygons(81, 58) + areaid_le = deck.polygons(81, 60) + areaid_hl = deck.polygons(81, 63) + areaid_sd = deck.polygons(81, 70) + areaid_po = deck.polygons(81, 81) + areaid_it = deck.polygons(81, 84) + areaid_et = deck.polygons(81, 101) + areaid_lvt = deck.polygons(81, 108) + areaid_re = deck.polygons(81, 125) + areaid_ag = deck.polygons(81, 79) + poly_rs = deck.polygons(66, 13) + diff_rs = deck.polygons(65, 13) + pwell_rs = deck.polygons(64, 13) + li_rs = deck.polygons(67, 13) + cfom = deck.polygons(22, 20) + + # Define a new custom function that selects polygons by their number of holes: + # It will return a new layer containing those polygons with min to max holes. + # max can be nil to omit the upper limit. + + # DRC section + ######################## + deck.info("DRC section") + + if FEOL: + deck.info("FEOL section") + gate = diff & poly + + # dnwell + dnwell.width(3.0, euclidian).output( + "dnwell.2", "dnwell.2 : min. dnwell width : 3.0um" + ) + dnwell.not_(uhvi).not_(areaid_po).isolated(6.3, euclidian).output( + "dnwell.3", "dnwell.3 : min. dnwell spacing : 6.3um" + ) + dnwell.and_(pnp).output("dnwell.4", "dnwell.4 : dnwell must not overlap pnp") + dnwell.and_(psdm).edges().not_(psdm.edges()).output( + "dnwell.5", "p+ must not straddle dnwell" + ) + # dnwell.6 rue not coded + + # nwell + nwell.width(0.84, euclidian).output( + "nwell.1", "nwell.1 : min. nwell width : 0.84um" + ) + nwell.isolated(1.27, euclidian).output( + "nwell.2a", "nwell.2a : min. nwell spacing (merged if less) : 1.27um" + ) + # rule nwell.4 is suitable for digital cells + # nwell.not(uhvi).not(areaid_en20).not_interacting(tap.and(licon).and(li)).output("nwell.4", "nwell4 : all nwell exempt inside uhvi must contain a n+tap") + nwell.enclosing(dnwell.not_(uhvi).not_(areaid_po), 0.4, euclidian).output( + "nwell.5", + "nwell.5 : min. nwell enclosing dnwell exempt unside uhvi : 0.4um", + ) + dnwell.enclosing(nwell.not_(uhvi), 1.03, euclidian).output( + "nwell.6", + "nwell.6 : min. dnwell enclosing nwell exempt unside uhvi : 1.03um", + ) + dnwell.separation(nwell, 4.5, euclidian).output( + "nwell.7", "nwell.7 : min. dnwell separation nwell : 4.5um" + ) + + # pwbm + pwbm.not_(uhvi).output("pwbm", "pwbm must be inside uhvi") + dnwell.and_(uhvi).edges().not_(pwbm).output( + "pwbm.4", "pwbm.4 : dnwell inside uhvi must be enclosed by pwbm" + ) + + # pwde + pwde.not_(pwbm).output("pwdem.3", "pwdem.3 : pwde must be inside pwbm") + pwde.not_(uhvi).output("pwdem.4", "pwdem.4 : pwde must be inside uhvi") + pwde.not_(dnwell).output("pwdem.5", "pwdem.5 : pwde must be inside dnwell") + + # hvtp + # hvtp.not(nwell).output("hvtp", "hvtp must inside nwell") + hvtp.width(0.38, euclidian).output( + "hvtp.1", "hvtp.1 : min. hvtp width : 0.38um" + ) + hvtp.isolated(0.38, euclidian).output( + "hvtp.2", "hvtp.2 : min. hvtp spacing : 0.38um" + ) + hvtp.enclosing(gate.and_(psdm), 0.18, euclidian).output( + "hvtp.3", "hvtp.3 : min. hvtp enclosure of pfet gate : 0.18um" + ) + hvtp.separation(gate.and_(psdm), 0.18, euclidian).output( + "hvtp.4", "hvtp.4 : min. hvtp spacing pfet gate: 0.18um" + ) + hvtp.with_area((0, 0.265)).output( + "hvtp.5", "hvtp.5 : min. hvtp area : 0.265um²" + ) + + # hvtr + hvtr.width(0.38, euclidian).output( + "hvtr.1", "hvtr.1 : min. hvtr width : 0.38um" + ) + hvtr.isolated(0.38, euclidian).output( + "hvtr.2", "hvtr.2 : min. hvtr spacing : 0.38um" + ) + + # lvtn + lvtn.width(0.38, euclidian).output( + "lvtn.1", "lvtn.1 : min. lvtn width : 0.38um" + ) + lvtn.isolated(0.38, euclidian).output( + "lvtn.2", "lvtn.2 : min. lvtn spacing : 0.38um" + ) + lvtn.separation(diff.and_(poly).not_(uhvi), 0.18, euclidian).output( + "lvtn.3a", "lvtn.3a : min. lvtn spacing to gate : 0.18um" + ) + lvtn.separation(diff.and_(nwell).not_(poly), 0.235, projection).output( + "lvtn.3b", "lvtn.3b : min. lvtn spacing to pfet s/d : 0.18um" + ) + lvtn.enclosing(gate.not_(uhvi), 0.18, euclidian).output( + "lvtn.4b", "lvtn.4b : min. lvtn enclosing to gate : 0.18um" + ) + lvtn.separation(hvtp, 0.38, euclidian).output( + "lvtn.9", "lvtn.9 : min. lvtn spacing hvtp : 0.38um" + ) + nwell.not_interacting(gate.and_(nwell.not_(hvi).not_(areaid_ce))).enclosing( + lvtn.not_(uhvi), 0.18, euclidian + ).polygons().without_area(0).output( + "lvtn.4b", "lvtn.4b : min. lvtn enclosure of gate : 0.18um" + ) + lvtn.separation(nwell.inside(areaid_ce), 0.38, euclidian).output( + "lvtn.12", "lvtn.12 : min. lvtn spacing nwell inside areaid.ce : 0.38um" + ) + lvtn.with_area((0, 0.265)).output( + "lvtn.13", "lvtn.13 : min. lvtn area : 0.265um²" + ) + + # ncm + ncm.and_(tap.and_(nwell).or_(diff.not_(nwell))).output( + "ncm.x.3", "ncm.x.3 : ncm must not overlap n+diff" + ) + ncm.width(0.38, euclidian).output("ncm.1", "ncm.1 : min. ncm width : 0.38um") + ncm.isolated(0.38, euclidian).output( + "ncm.2", "ncm.2 : min. ncm spacing manual merge if smaller : 0.38um" + ) + ncm.enclosing(diff.and_(nwell), 0.18, euclidian).output( + "ncm.3", "ncm.3 : min. ncm enclosure of p+diff : 0.18um" + ) + ncm.separation(lvtn.and_(diff), 0.23, euclidian).output( + "ncm.5", "ncm.5 : min. ncm spacing lvtn diff : 0.23um" + ) + ncm.separation(diff.not_(nwell), 0.2, euclidian).output( + "ncm.6", "ncm.6 : min. ncm spacing nfet : 0.2um" + ) + ncm.with_area((0, 0.265)).output("ncm.7", "ncm.13 : min. ncm area : 0.265um²") + + # diff-tap + difftap = diff + tap + difftap.width(0.15, euclidian).output( + "difftap.1", "difftap.1 : min. difftap width : 0.15um" + ) + not_in_cell1 = deck.select( + "s8cell_ee_plus_sseln_a", + "-s8cell_ee_plus_sseln_b", + "-s8cell_ee_plus_sselp_a", + "-s8cell_ee_plus_sselp_b", + "-s8fpls_pl8", + "-s8fpls_rdrv4", + "-s8fpls_rdrv4f", + "-s8fpls_rdrv8", + ) + not_in_cell1_diff = not_in_cell1.input(65, 20) + not_in_cell1_diff.not_(areaid_sc).not_(poly).edges().and_( + gate.edges() + ).with_length(0, 0.42).output( + "difftap.2", "difftap.2: min. gate (exempt areaid.sc) width : 0.42um" + ) + diff.and_(areaid_sc).not_(poly).edges().and_(gate.edges()).with_length( + 0, 0.36 + ).output("difftap.2", "difftap.2: min. gate inside areaid.sc width : 0.36um") + difftap.isolated(0.27, euclidian).output( + "difftap.3", "difftap.3 : min. difftap spacing : 0.27um" + ) + tap.edges().and_(diff.edges()).with_length(0, 0.29).output( + "difftap.4", "difftap.4 : min. tap bound by diffusion : 0.29um" + ) + tap.edges().and_(diff.edges()).space(0.4, projection).output( + "difftap.5", "difftap.5 : min. tap bound by 2 diffusions : 0.4um" + ) + (tap.edges().and_(diff.edges())).extended(0.01, 0.01, 0, 0, False).not_( + tap.and_(diff) + ).and_(diff.or_(tap)).output( + "difftap.6", + "difftap.6 : diff and tap not allowed to extend beyong their abutting ege", + ) + tap.edges().not_interacting(diff.edges()).separation( + diff.edges(), 0.13, euclidian + ).output( + "difftap.7", + "difftap.7 : min. diff/tap spacing to non-coincident diff edge : 0.13um", + ) + diff.edges().not_interacting(tap.edges()).separation( + tap.edges(), 0.13, euclidian + ).output( + "difftap.7", + "difftap.7 : min. diff/tap spacing to non-coincident tap edge : 0.13um", + ) + nwell.enclosing(diff.not_(uhvi).and_(psdm), 0.18, euclidian).output( + "difftap.8", "difftap.8 : min. p+diff enclosure by nwell : 0.18um" + ) + diff.not_(uhvi).and_(nsdm).separation(nwell, 0.34, euclidian).output( + "difftap.9", "difftap.9 : min. n+diff spacing to nwell : 0.34um" + ) + nwell.enclosing(tap.not_(uhvi).and_(nsdm), 0.18, euclidian).output( + "difftap.10", "difftap.10 : min. n+tap enclosure by nwell : 0.18um" + ) + tap.not_(uhvi).and_(psdm).separation(nwell, 0.13, euclidian).output( + "difftap.11", "difftap.11 : min. p+tap spacing to nwell : 0.13um" + ) + + # tunm + tunm.width(0.41, euclidian).output( + "tunm.1", "tunm.1 : min. tunm width : 0.41um" + ) + tunm.isolated(0.5, euclidian).output( + "tunm.2", "tunm.2 : min. tunm spacing : 0.5um" + ) + tunm.enclosing(gate, 0.095, euclidian).output( + "tunm.3", "tunm.3 : min. tunm beyond gate : 0.095um" + ) + tunm.separation(gate.not_interacting(tunm), 0.095, euclidian).output( + "tunm.4", "tunm.4 : min. tunm spacing to gate outside tunm: 0.095um" + ) + gate.and_(tunm).edges().not_(gate.edges()).output( + "tunm.5", "tunm.5 : gate must not straddle tunm" + ) + tunm.not_(dnwell).output("tunm.6a", "tunm.6a : tunm not allowed outside dnwell") + tunm.with_area((0, 0.672)).output( + "tunm.7", "tunm.7 : min. tunm area : 0.672um²" + ) + + # poly + poly.width(0.15, euclidian).output( + "poly.1a", "poly.1a : min. poly width : 0.15um" + ) + poly.not_(diff).edges().and_(gate.and_(lvtn).edges()).space( + 0.35, euclidian + ).output("poly.1b", "poly.1b: min. lvtn gate width : 0.35um") + poly.isolated(0.21, euclidian).output( + "poly.2", "poly.2 : min. poly spacing : 0.21um" + ) + poly.and_(rpm.or_(urpm).or_(poly_rs)).width(0.33, euclidian).output( + "poly.3", "poly.3 : min. poly resistor width : 0.33um" + ) + poly.not_(gate).separation(diff, 0.075, projection).polygons().without_area( + 0 + ).output("poly.4", "poly.4 : min. poly on field spacing to diff : 0.075um") + poly.not_(gate).separation(tap, 0.055, euclidian).output( + "poly.5", "poly.5 : min. poly on field spacing to tap : 0.055um" + ) + gate.separation(tap, 0.3, projection).polygons().and_(diff).output( + "poly.6", "poly.6 : min. gate spacing to tap : 0.3um" + ) + diff.enclosing(gate, 0.25, projection).polygons().without_area(0).output( + "poly.7", "poly.7 : min. source/drain length : 0.25um" + ) + poly.enclosing(gate, 0.13, projection).polygons().without_area(0).output( + "poly.8", "poly.8 : min. poly extention gate (endcap) : 0.13um" + ) + poly.and_(rpm.or_(urpm).or_(poly_rs)).separation( + poly.or_(difftap), 0.48, euclidian + ).polygons().without_area(0).output( + "poly.9", "poly.9 : min. poly resistor space to poly or diff/tap : 0.48um" + ) + diff.merged().edges().end_segments(0.01).and_(poly).output( + "poly.10", "poly.10 : poly must not overlap diff corner" + ) + gate.with_angle((0, 90)).output("poly.11", "poly.11 : non 90 degree angle gate") + not_in_cell3 = deck.select("s8fgvr_n_fg2") + not_in_cell3_poly = not_in_cell3.input(66, 20) + not_in_cell3_poly.not_(hvi).not_(nwell.not_(hvi)).and_(tap).output( + "poly.12", "poly.12 : poly must not overlap tap" + ) + poly.and_(diff_rs).output( + "poly.15", "poly.15 : poly must not overlap diff resistor" + ) + + # rpm + rpm.width(1.27, euclidian).output("rpm.1a", "rpm.1a : min. rpm width : 1.27um") + rpm.isolated(0.84, euclidian).output( + "rpm.2", "rpm.2 : min. rpm spacing : 0.84um" + ) + rpm.enclosing(poly.and_(poly_rs).and_(psdm), 0.2, euclidian).output( + "rpm.3", "rpm.3 : min. rpm enclosure of poly resistor : 0.2um" + ) + psdm.enclosing(poly.and_(poly_rs).and_(rpm), 0.11, euclidian).output( + "rpm.4", "rpm.4 : min. psdm enclosure of poly resistor : 0.11um" + ) + npc.enclosing(poly.and_(poly_rs).and_(rpm), 0.095, euclidian).output( + "rpm.5", "rpm.5 : min. npc enclosure of poly resistor : 0.095um" + ) + rpm.separation(nsdm, 0.2, euclidian).output( + "rpm.6", "rpm.6 : min. rpm spacing nsdm: 0.2um" + ) + rpm.separation(poly, 0.2, euclidian).output( + "rpm.7", "rpm.7 : min. rpm spacing poly: 0.2um" + ) + rpm.and_(poly).edges().not_(poly.edges()).output( + "rpm.8", "rpm.8 : poly must not straddle rpm" + ) + poly.and_(poly_rs).and_(rpm).separation(hvntm, 0.185, euclidian).output( + "rpm.9", "rpm.9 : min. poly resistor spacing hvntm: 0.185um" + ) + rpm.and_(pwbm).output("rpm.10", "rpm.107 : min. rpm spacing pwbm: na") + + # varac + varac = poly & tap & (nwell - hvi) - areaid_ce + tap.not_(poly).edges().and_(varac.edges()).space(0.18, euclidian).output( + "varac.1", "varac.1: min. varac channel length : 0.18um" + ) + tap.and_(poly).edges().and_(varac.edges()).space(1.0, euclidian).output( + "varac.2", "varac.2: min. varac channel wdth : 1.0um" + ) + varac.separation(hvtp, 0.18, euclidian).output( + "varac.3", "varac.3: min. varac channel space to hvtp : 0.18um" + ) + varac.separation(licon.and_(tap), 0.25, euclidian).output( + "varac.4", "varac.4: min. varac channel space to licon on tap : 0.25um" + ) + nwell.enclosing(poly.overlapping(varac), 0.15, euclidian).output( + "varac.5", + "varac.5: min. nwell enclosure of poly overlapping varac channel : 0.15um", + ) + tap.overlapping(varac).separation( + difftap, 0.27, euclidian + ).polygons().without_area(0).output( + "varac.6", "varac.6: min. varac channel tap space to difftap : 0.27um" + ) + nwell.overlapping(varac).and_(diff.and_(nwell)).output( + "varac.7", + "varac.7: nwell overlapping varac channel must not overlap p+diff", + ) + + # photo + photodiode = dnwell & areaid_po + photodiode.edges().without_length(3.0).output( + "photo.2", "photo.2 : minimum/maximum width of photodiode : 3.0um" + ) + photodiode.isolated(5.0, euclidian).output( + "photo.3", "photo.3 : mini. photodiode spacing : 5.0um" + ) + photodiode.separation(dnwell, 5.3, euclidian).output( + "photo.4", "photo.4 : mini. photodiode spacing to dnwell : 5.3um" + ) + areaid_po.not_(dnwell).output( + "photo.5.6", + "photo.5.6 : photodiode edges must coincide areaid.po and enclosed by dnwell", + ) + photodiode.not_(tap.not_(nwell).holes()).output( + "photo.7", "photo.7 : photodiode must be enclosed by p+tap ring" + ) + photodiode.and_(nwell).edges().without_length(0.84).output( + "photo.8", + "photo.8 : minimum/maximum width of nwell inside photodiode : 0.84um", + ) + areaid_po.edges().and_(photodiode.and_(nwell).sized(1.08)).without_length( + 12.0 + ).output( + "photo.9", + "photo.9 : minimum/maximum enclosure of nwell by photodiode : 1.08um", + ) + photodiode.and_(tap).edges().without_length(0.41).output( + "photo.10", + "photo.10 : minimum/maximum width of tap inside photodiode : 0.41um", + ) + + # npc + npc.width(0.27, euclidian).output("npc.1", "npc.1 : min. npc width : 0.27um") + npc.isolated(0.27, euclidian).output( + "npc.2", + "npc.2 : min. npc spacing, should be mnually merge if less : 0.27um", + ) + npc.separation(gate, 0.09, euclidian).output( + "npc.4", "npc.4 : min. npc spacing to gate : 0.09um" + ) + + # nsdm/psdm + npsdm = nsdm + psdm + nsdm.width(0.38, euclidian).output( + "nsdm.1", "nsdm.1 : min. nsdm width : 0.38um" + ) + psdm.width(0.38, euclidian).output( + "psdm.1", "psdm.1 : min. psdm width : 0.38um" + ) + nsdm.isolated(0.38, euclidian).output( + "n/psdm.1", + "n/psdm.1 : min. nsdm spacing, should be mnually merge if less : 0.38um", + ) + psdm.isolated(0.38, euclidian).output( + "n/psdm.1", + "n/psdm.1 : min. psdm spacing, should be mnually merge if less : 0.38um", + ) + npsdm.enclosing(diff, 0.125, euclidian).polygons().not_( + tap.sized(0.125) + ).output( + "n/psdm.5a", + "n/psdm.5a : min. n/psdm enclosure diff except butting edge : 0.125um", + ) + npsdm.enclosing(tap, 0.125, euclidian).polygons().not_( + diff.sized(0.125) + ).output( + "n/psdm.5b", + "n/psdm.5b : min. n/psdm enclosure tap except butting edge : 0.125um", + ) + tap.edges().and_(diff.edges()).not_(npsdm).output( + "n/psdm.6", "n/psdm.6 : min. n/psdm enclosure of butting edge : 0.0um" + ) + nsdm.and_(difftap).separation( + psdm.and_(difftap), 0.13, euclidian + ).polygons().without_area(0).output( + "n/psdm.7", + "n/psdm.7 : min. nsdm diff spacing to psdm diff except butting edge : 0.13um", + ) + diff.and_((nsdm.and_(nwell)).or_(psdm.not_(nwell))).output( + "n/psdm.8", + "n/psdm.8 : diff should be the opposite type of well/substrate underneath", + ) + tap.and_((nsdm.not_(nwell)).or_(psdm.and_(nwell))).output( + "n/psdm.8", + "n/psdm.8 : tap should be the same type of well/substrate underneath", + ) + tap.and_(diff).without_area(0).output( + "tap and diff", "tap and diff must not overlap" + ) + nsdm.with_area((0, 0.265)).output( + "n/psdm.10a", "n/psdm.10a : min. nsdm area : 0.265um²" + ) + psdm.with_area((0, 0.265)).output( + "n/psdm.10b", "n/psdm.10b : min. psdm area : 0.265um²" + ) + + # licon + licon.not_(poly.interacting(poly_rs).and_(rpm)).edges().without_length( + 0.17 + ).output("licon.1", "licon.1 : minimum/maximum width of licon : 0.17um") + licon.and_(poly.interacting(poly_rs).and_(rpm)).not_interacting( + ( + licon.and_(poly.interacting(poly_rs).and_(rpm)) + .edges() + .with_length(0.19) + ).or_( + licon.and_(poly.interacting(poly_rs).and_(rpm)).edges().with_length(2.0) + ) + ).output( + "licon.1b/c", + "licon.1b/c : minimum/maximum width/length of licon inside poly resistor : 2.0/0.19um", + ) + licon.isolated(0.17, euclidian).output( + "licon.2", "licon.2 : min. licon spacing : 0.17um" + ) + licon.and_(poly.interacting(poly_rs).and_(rpm)).edges().with_length(0.19).space( + 0.35, euclidian + ).output( + "licon.2b", "licon.2b : min. licon 0.19um edge on resistor spacing : 0.35um" + ) + licon.interacting( + licon.and_(poly.interacting(poly_rs).and_(rpm)).edges().with_length(2.0) + ).separation( + licon.and_(poly.interacting(poly_rs).and_(rpm)), 0.51, euclidian + ).output( + "licon.2c", "licon.2c : min. licon 2.0um edge on resistor spacing : 0.51um" + ) + licon.and_(poly.interacting(poly_rs).and_(rpm)).separation( + licon.not_(poly.interacting(poly_rs).and_(rpm)), 0.51, euclidian + ).output( + "licon.2d", "licon.2d : min. licon on resistor spacing other licon : 0.51um" + ) + # rule licon.3 not coded + licon.not_(li).not_(poly.or_(diff).or_(tap)).output( + "licon.4", "licon.4 : min. licon must overlap li and (poly or tap or diff) " + ) + diff.enclosing(licon, 0.04, euclidian).output( + "licon.5", "licon.5 : min. diff enclosure of licon : 0.04um" + ) + tap.edges().and_(diff.edges()).separation( + licon.and_(tap).edges(), 0.06, euclidian + ).output( + "licon.6", "licon.6 : min. abutting edge spacing to licon tap : 0.06um" + ) + licon_edges_with_less_enclosure_tap = tap.enclosing( + licon, 0.12, projection + ).second_edges() + opposite1 = ( + (licon.edges() - licon_edges_with_less_enclosure_tap) + .width(0.17 + 1 * deck.dbu, projection) + .polygons() + ) + licon.not_interacting(opposite1).output( + "licon.7", + "licon.7 : min. tap enclosure of licon by one of 2 opposite edges : 0.12um", + ) + poly.enclosing(licon, 0.05, euclidian).output( + "licon.8", "licon.8 : min. poly enclosure of licon : 0.05um" + ) + licon008 = licon.interacting(poly.enclosing(licon, 0.08, euclidian).polygons()) + licon_edges_with_less_enclosure_poly = poly.enclosing( + licon, 0.08, projection + ).second_edges() + opposite2 = ( + (licon.edges() - licon_edges_with_less_enclosure_poly) + .width(0.17 + 1 * deck.dbu, projection) + .polygons() + ) + licon008.not_interacting(opposite2).output( + "licon.8a", + "licon.8a : min. poly enclosure of licon by one of 2 opposite edges : 0.08um", + ) + # rule licon.9 not coded + licon.and_(tap.and_(nwell.not_(hvi))).separation(varac, 0.25, euclidian).output( + "licon.10", "licon.10 : min. licon spacing to varac channel : 0.25um" + ) + not_in_cell4 = deck.select( + "-s8fs_gwdlvx4", + "-s8fs_gwdlvx8", + "-s8fs_hvrsw_x4", + "-s8fs_hvrsw8", + "-s8fs_hvrsw264", + "-s8fs_hvrsw520", + "-s8fs_rdecdrv", + "-s8fs_rdec8”, “s8fs_rdec32", + "-s8fs_rdec264", + "-s8fs_rdec520", + ) + not_in_cell4_licon = not_in_cell4.input(66, 44) + not_in_cell4_licon.and_(diff.or_(tap)).separation( + gate.not_(areaid_sc), 0.055, euclidian + ).output("licon.11", "licon.11 : min. licon spacing to gate : 0.055um") + licon.and_(diff.or_(tap)).separation( + gate.and_(areaid_sc), 0.05, euclidian + ).output( + "licon.11a", + "licon.11a : min. licon spacing to gate inside areaid.sc : 0.05um", + ) + in_cell4 = deck.select( + "+s8fs_gwdlvx4", + "+s8fs_gwdlvx8", + "+s8fs_hvrsw_x4", + "+s8fs_hvrsw8", + "+s8fs_hvrsw264", + "+s8fs_hvrsw520", + ) + in_cell4_licon = in_cell4.input(66, 44) + in_cell4_licon.and_(diff.or_(tap)).separation(gate, 0.04, euclidian).output( + "licon.11c", + "licon.11c : min. licon spacing to gate for specific cells: 0.04um", + ) + # rules 11.b , 11.d not coded + diff.interacting(gate).not_( + diff.interacting(gate).width(5.7, euclidian).polygons() + ).output("licon.12", "licon.12 : max. sd width without licon : 5.7um") + licon.and_(diff.or_(tap)).separation(npc, 0.09, euclidian).output( + "licon.13", "licon.13 : min. difftap licon spacing to npc : 0.09um" + ) + licon.and_(poly).separation(diff.or_(tap), 0.19, euclidian).output( + "licon.14", "licon.14 : min. poly licon spacing to difftap : 0.19um" + ) + npc.enclosing(licon.and_(poly), 0.1, euclidian).output( + "licon.15", "licon.15 : min. npc enclosure of poly-licon : 0.1um" + ) + # rule licon.16 not applicable for the diff for the nmos of a nand gates or the pmos of a nor gates + # diff.not(gate).not_interacting(licon).output("licon.16", "licon.16 : diff must enclose one licon") + tap.not_(uhvi).not_interacting(licon).output( + "licon.16", "licon.16 : tap must enclose one licon" + ) + poly.and_(tap).edges().not_(tap.edges()).output( + "licon.17", "licon.17 : tap must not straddle poly" + ) + npc.not_interacting(licon.and_(poly)).output( + "licon.18", "licon.18 : npc mut enclosed one poly-licon" + ) + + # vpp + vpp.width(1.43, euclidian).output("vpp.1", "vpp.1 : min. vpp width : 1.43um") + # rules 1.b, 1.c not coded + vpp.and_(poly.or_(difftap)).output( + "vpp.3", "vpp.3 : vpp must not overlapp poly or diff or tap" + ) + vpp.and_(nwell).edges().not_(vpp.edges()).output( + "vpp.4", "vpp.4 : vpp must not straddle nwell" + ) + vpp.and_(dnwell).edges().not_(vpp.edges()).output( + "vpp.4", "vpp.4 : vpp must not straddle dnwell" + ) + vpp.and_(poly.or_(li).or_(m1).or_(m2)).separation( + poly.or_(li).or_(m1).or_(m2), 1.5, euclidian + ).polygons().with_area(2.25, None).output( + "vpp.5", "vpp.5 : min. vpp spacing to poly or li or m1 or m2 : 1.5um" + ) + vpp.with_area((0, area(vpp.and_(m3)) * 0.25)).output( + "vpp.5a", "vpp.5a : max. m3 density in vpp : 0.25" + ) + vpp.with_area((0, area(vpp.and_(m4)) * 0.3)).output( + "vpp.5b", "vpp.5b : max. m4 density in vpp : 0.3" + ) + vpp.with_area((0, area(vpp.and_(m5)) * 0.4)).output( + "vpp.5c", "vpp.5c : max. m5 density in vpp : 0.4" + ) + nwell.enclosing(vpp, 1.5, euclidian).output( + "vpp.8", "vpp.8 : nwell enclosure of vpp : 1.5" + ) + vpp.separation(nwell, 1.5, euclidian).polygons().without_area(0).output( + "vpp.9", "vpp.9 : vpp spacing to nwell : 1.5" + ) + # rule vpp.10 not coded + # rule vpp.11 not coded because moscap is not defined properly by any gds layer + # rules vpp.12a, 12b, 12c not coded because specific to one cell + if CU: + m1.separation(vpp.and_(m1), 0.16, euclidian).polygons().without_area( + 0 + ).output("vpp.13", "vpp.13 : m1 spacing to m1inside vpp : 0.16") + + # CAPM + capm.width(1.0, euclidian).output("capm.1", "capm.1 : min. capm width : 1.0um") + capm.isolated(0.84, euclidian).output( + "capm.2a", "capm.2a : min. capm spacing : 0.84um" + ) + m2.interacting(capm).isolated(1.2, euclidian).output( + "capm.2b", "capm.2b : min. capm spacing : 1.2um" + ) + m2.enclosing(capm, 0.14, euclidian).output( + "capm.3", "capm.3 : min. m2 enclosure of capm : 0.14um" + ) + capm.enclosing(via2, 0.14, euclidian).output( + "capm.4", "capm.4 : min. capm enclosure of via2 : 0.14um" + ) + capm.separation(via2, 0.14, euclidian).output( + "capm.5", "capm.5 : min. capm spacing to via2 : 0.14um" + ) + capm.sized(-20.0).sized(20.0).output( + "capm.6", "capm.6 : max. capm lenght/width : 20um" + ) + capm.with_angle((0, 90)).output("capm.7", "capm.7 : capm not rectangle") + capm.separation(via, 0.14, euclidian).polygons().without_area(0).output( + "capm.8", "capm.8 : min. capm spacing to via : 0.14um" + ) + capm.and_(nwell).edges().not_(capm.edges()).output( + "capm.10", "capm.10 : capm must not straddle nwell" + ) + capm.and_(diff).edges().not_(capm.edges()).output( + "capm.10", "capm.10 : capm must not straddle diff" + ) + capm.and_(tap).edges().not_(capm.edges()).output( + "capm.10", "capm.10 : capm must not straddle tap" + ) + capm.and_(poly).edges().not_(capm.edges()).output( + "capm.10", "capm.10 : capm must not straddle poly" + ) + capm.and_(li).edges().not_(capm.edges()).output( + "capm.10", "capm.10 : capm must not straddle li" + ) + capm.and_(m1).edges().not_(capm.edges()).output( + "capm.10", "capm.10 : capm must not straddle m1" + ) + capm.separation(m2.not_interacting(capm), 0.14, euclidian).output( + "capm.11", "capm.11 : min. capm spacing to m2 not overlapping capm : 0.5um" + ) + + if BEOL: + deck.info("BEOL section") + + # li + not_in_cell5 = deck.select("-s8rf2_xcmvpp_hd5_*") + not_in_cell5_li = not_in_cell5.polygons(li_wildcard) + not_in_cell5_li.width(0.17, euclidian).output( + "li.1", "li.1 : min. li width : 0.17um" + ) + + in_cell5_li = li - not_in_cell5_li + in_cell5_li.width(0.14, euclidian).output( + "li.1a", "li.1a : min. li width for the cells s8rf2_xcmvpp_hd5_* : 0.14um" + ) + + # rule li.2 not coded + + not_in_cell5_li.space(0.17, euclidian).output( + "li.3", "li.3 : min. li spacing : 0.17um" + ) + in_cell5_li.space(0.14, euclidian).output( + "li.3a", "li.3a : min. li spacing for the cells s8rf2_xcmvpp_hd5_* : 0.14um" + ) + licon08 = licon.interacting(li.enclosing(licon, 0.08, euclidian).polygons()) + licon_edges_with_less_enclosure_li = li.enclosing( + licon, 0.08, projection + ).second_edges() + opposite3 = ( + (licon.edges() - licon_edges_with_less_enclosure_li) + .width(0.17 + 1 * deck.dbu, projection) + .polygons() + ) + licon08.not_interacting(opposite3).output( + "li.5", "li.5 : min. li enclosure of licon of 2 opposite edges : 0.08um" + ) + li.with_area((0, 0.0561)).output("li.6", "li.6 : min. li area : 0.0561um²") + + # ct + mcon.edges().without_length(0.17).output( + "ct.1", "ct.1 : minimum/maximum width of mcon : 0.17um" + ) + mcon.space(0.19, euclidian).output("ct.2", "ct.2 : min. mcon spacing : 0.19um") + # rule ct.3 not coded + mcon.not_(li).output("ct.4", "ct.4 : mcon should covered by li") + if CU: + li.interacting(with_holes(li.and_(m1).not_(mcon), 1, 10)).enclosing( + mcon, 0.2, euclidian + ).output( + "ct.irdrop.1", "ct.irdrop.1 : min. li enclsoure of 1..10 mcon : 0.2um" + ) + li.interacting(with_holes(li.and_(m1).not_(mcon), 11, 100)).enclosing( + mcon, 0.3, euclidian + ).output( + "ct.irdrop.2", "ct.irdrop.2 : min. li enclsoure of 11..100 mcon : 0.3um" + ) + + # m1 + m1.width(0.14, euclidian).output("m1.1", "m1.1 : min. m1 width : 0.14um") + + huge_m1 = m1.sized(-1.5).sized(1.5) + non_huge_m1 = m1 - huge_m1 + + non_huge_m1.space(0.14, euclidian).output( + "m1.2", "m1.2 : min. m1 spacing : 0.14um" + ) + + ( + huge_m1.separation(non_huge_m1, 0.28, euclidian) + + huge_m1.space(0.28, euclidian) + ).output("m1.3ab", "m1.3ab : min. 3um.m1 spacing m1 : 0.28um") + + not_in_cell6 = deck.select( + "-s8cell_ee_plus_sseln_a", + "-s8cell_ee_plus_sseln_b", + "-s8cell_ee_plus_sselp_a", + "-s8cell_ee_plus_sselp_b", + "-s8fpls_pl8", + "-s8fs_cmux4_fm", + ) + not_in_cell6_m1 = not_in_cell6.input(m1_wildcard) + not_in_cell6_m1.enclosing(mcon, 0.03, euclidian).output( + "m1.4", "m1.4 : min. m1 enclosure of mcon : 0.03um" + ) + in_cell6 = deck.select( + "+s8cell_ee_plus_sseln_a", + "+s8cell_ee_plus_sseln_b", + "+s8cell_ee_plus_sselp_a", + "+s8cell_ee_plus_sselp_b", + "+s8fpls_pl8", + "+s8fs_cmux4_fm", + ) + in_cell6_m1 = in_cell6.input(m1_wildcard) + in_cell6_m1.enclosing(mcon, 0.005, euclidian).output( + "m1.4a", "m1.4a : min. m1 enclosure of mcon for specific cells : 0.005um" + ) + m1.with_area((0, 0.083)).output("m1.6", "m1.6 : min. m1 area : 0.083um²") + m1.holes().with_area((0, 0.14)).output( + "m1.7", "m1.7 : min. m1 holes area : 0.14um²" + ) + if not CU: + mcon06 = mcon.interacting(poly.enclosing(m1, 0.06, euclidian).polygons()) + mcon_edges_with_less_enclosure_m1 = m1.enclosing( + mcon, 0.06, projection + ).second_edges() + opposite4 = ( + (mcon.edges() - mcon_edges_with_less_enclosure_m1) + .width(0.17 + 1 * deck.dbu, projection) + .polygons() + ) + mcon06.not_interacting(opposite4).output( + "m1.5", "m1.5 : min. m1 enclosure of mcon of 2 opposite edges : 0.06um" + ) + # rule m1.pd.1, rule m1.pd.2a, rule m1.pd.2b not coded + if CU: + m1.sized(-2.0).sized(2.0).output( + "m1.11", "m1.11 : max. m1 width after slotting : 4.0um" + ) + # rule m1.12 not coded because inconsistent with m1.11 + # rule m1.13, m1.14, m1.14a not coded : see : https://www.klayout.de/forum/discussion/comment/6759 + + # via + # rule via.3 not coded + via.not_(m1).output("via.4c.5c", "via.4c.5c : m1 must enclose all via") + if not CU: + via.not_(areaid_mt).edges().without_length(0.15).output( + "via.1a", "via.1a : minimum/maximum width of via : 0.15um" + ) + via.and_(areaid_mt).not_interacting( + (via.and_(areaid_mt).edges().without_length(0.15)) + .or_(via.and_(areaid_mt).edges().without_length(0.23)) + .or_(via.and_(areaid_mt).edges().without_length(0.28)) + ).output( + "via.1b", + "via.1b : minimum/maximum width of via in areaid.mt: 0.15um or 0.23um or 0.28um", + ) + via.isolated(0.17, euclidian).output( + "via.2", "via.2 : min. via spacing : 0.17um" + ) + m1.enclosing( + via.not_interacting(via.edges().without_length(0.15)), 0.055, euclidian + ).output("via.4a", "via.4a : min. m1 enclosure of 0.15um via : 0.055um") + m1.enclosing( + via.not_interacting(via.edges().without_length(0.23)), 0.03, euclidian + ).output("via.4b", "via.4b : min. m1 enclosure of 0.23um via : 0.03um") + via1_edges_with_less_enclosure_m1 = m1.enclosing( + via.not_interacting(via.edges().without_length(0.15)), 0.085, projection + ).second_edges() + opposite5 = ( + ( + via.not_interacting(via.edges().without_length(0.15)).edges() + - via1_edges_with_less_enclosure_m1 + ) + .width(0.15 + 1 * deck.dbu, projection) + .polygons() + ) + via.not_interacting(via.edges().without_length(0.15)).not_interacting( + opposite5 + ).output( + "via1.5a", + "via1.5a : min. m1 enclosure of 0.15um via of 2 opposite edges : 0.085um", + ) + via2_edges_with_less_enclosure_m1 = m1.enclosing( + via.not_interacting(via.edges().without_length(0.23)), 0.06, projection + ).second_edges() + opposite6 = ( + ( + via.not_interacting(via.edges().without_length(0.23)).edges() + - via2_edges_with_less_enclosure_m1 + ) + .width(0.23 + 1 * deck.dbu, projection) + .polygons() + ) + via.not_interacting(via.edges().without_length(0.23)).not_interacting( + opposite6 + ).output( + "via1.5b", + "via1.5b : min. m1 enclosure of 0.23um via of 2 opposite edges : 0.06um", + ) + if CU: + via.not_(areaid_mt).edges().without_length(0.18).output( + "via.11", "via.11 : minimum/maximum width of via : 0.18um" + ) + via.isolated(0.13, euclidian).output( + "via.12", "via.12 : min. via spacing : 0.13um" + ) + # rule via.13 not coded because not understandable + via1_edges_with_less_enclosure_m1 = m1.enclosing( + via, 0.04, projection + ).second_edges() + opposite5 = ( + (via.edges() - via1_edges_with_less_enclosure_m1) + .width(0.18 + 1 * deck.dbu, projection) + .polygons() + ) + via.not_interacting(opposite5).output( + "via1.14", + "via1.14 : min. m1 enclosure of 0.04um via of 2 opposite edges : 0.04um", + ) + # rules via.irdrop.1, via.irdrop.2, via.irdrop.3, via.irdrop.4 not coded because not understandable + + # m2 + m2.width(0.14, euclidian).output("m2.1", "m2.1 : min. m2 width : 0.14um") + + huge_m2 = m2.sized(-1.5).sized(1.5) + non_huge_m2 = m2 - huge_m2 + + non_huge_m2.space(0.14, euclidian).output( + "m2.2", "m2.2 : min. m2 spacing : 0.14um" + ) + + ( + huge_m2.separation(non_huge_m2, 0.28, euclidian) + + huge_m2.space(0.28, euclidian) + ).output("m2.3ab", "m2.3ab : min. 3um.m2 spacing m2 : 0.28um") + + # rule m2.3c not coded + m2.with_area((0, 0.0676)).output("m2.6", "m2.6 : min. m2 area : 0.0676um²") + m2.holes().with_area((0, 0.14)).output( + "m2.7", "m2.7 : min. m2 holes area : 0.14um²" + ) + via.not_(m2).output("m2.via", "m2.via : m2 must enclose via") + if not CU: + m2.enclosing(via, 0.055, euclidian).output( + "m2.4", "m2.4 : min. m2 enclosure of via : 0.055um" + ) + via_edges_with_less_enclosure_m2 = m2.enclosing( + via, 0.085, projection + ).second_edges() + opposite7 = ( + (via.edges() - via_edges_with_less_enclosure_m2) + .width(0.2 + 1 * deck.dbu, projection) + .polygons() + ) + via.not_interacting(opposite7).output( + "m2.5", "m2.5 : min. m2 enclosure of via of 2 opposite edges : 0.085um" + ) + # rule m2.pd.1, rule m2.pd.2a, rule m2.pd.2b not coded + if CU: + m2.sized(-2.0).sized(2.0).output( + "m2.11", "m2.11 : max. m2 width after slotting : 4.0um" + ) + # rule m2.12 not coded because inconsistent with m2.11 + # rule m2.13, m2.14, m2.14a not coded : see : https://www.klayout.de/forum/discussion/comment/6759 + + # via2 + # rule via233 not coded + via2.not_(m2).output("via2", "via2 : m2 must enclose all via2") + if not CU: + via2.not_(areaid_mt).edges().without_length(0.2).output( + "via2.1a", "via2.1a : minimum/maximum width of via2 : 0.2um" + ) + via2.and_(areaid_mt).not_interacting( + (via2.and_(areaid_mt).edges().without_length(0.2)) + .or_(via2.and_(areaid_mt).edges().without_length(1.2)) + .or_(via2.and_(areaid_mt).edges().without_length(1.5)) + ).output( + "via2.1b", + "via2.1b : minimum/maximum width of via2 in areaid.mt: 0.2um or 1.2um or 1.5um", + ) + via2.isolated(0.2, euclidian).output( + "via2.2", "via2.2 : min. via2 spacing : 0.2um" + ) + m2.enclosing(via2, 0.04, euclidian).output( + "via2.4", "via2.4 : min. m2 enclosure of via2 : 0.04um" + ) + m2.enclosing( + via2.not_interacting(via2.edges().without_length(1.5)), 0.14, euclidian + ).output("via2.4a", "via2.4a : min. m2 enclosure of 1.5um via2 : 0.14um") + via2_edges_with_less_enclosure_m2 = m2.enclosing( + via2, 0.085, projection + ).second_edges() + opposite8 = ( + (via2.edges() - via2_edges_with_less_enclosure_m2) + .width(0.2 + 1 * deck.dbu, projection) + .polygons() + ) + via2.not_interacting(opposite8).output( + "via2.5", + "via2.5 : min. m2 enclosure of via2 of 2 opposite edges : 0.085um", + ) + if CU: + via2.edges().without_length(0.21).output( + "via2.11", "via2.11 : minimum/maximum width of via2 : 0.21um" + ) + via2.isolated(0.18, euclidian).output( + "via2.12", "via2.12 : min. via2 spacing : 0.18um" + ) + # rule via2.13 not coded because not understandable, or not clear + m2.enclosing(via2, 0.035, euclidian).output( + "via2.14", "via2.14 : min. m2 enclosure of via2 : 0.035um" + ) + # rules via2.irdrop.1, via2.irdrop.2, via2.irdrop.3, via2.irdrop.4 not coded because not understandable + + # m3 + m3.width(0.3, euclidian).output("m3.1", "m3.1 : min. m3 width : 0.3um") + + huge_m3 = m3.sized(-1.5).sized(1.5) + non_huge_m3 = m3 - huge_m3 + + non_huge_m3.space(0.3, euclidian).output( + "m3.2", "m3.2 : min. m3 spacing : 0.3um" + ) + + ( + huge_m3.separation(non_huge_m3, 0.4, euclidian) + + huge_m3.space(0.4, euclidian) + ).output("m3.3ab", "m3.3ab : min. 3um.m3 spacing m3 : 0.4um") + + # rule m3.3c not coded + m3.with_area((0, 0.24)).output("m3.6", "m3.6 : min. m3 area : 0.24um²") + via2.not_(m3).output("m3.via2", "m3.via2 : m3 must enclose via2") + if not CU: + m3.enclosing(via2, 0.065, euclidian).output( + "m3.4", "m3.4 : min. m3 enclosure of via2 : 0.065um" + ) + via2_edges_with_less_enclosure_m3 = m3.enclosing( + via2, 0.085, projection + ).second_edges() + # m3.5 N/A + # opposite9 = (via2.edges - via2_edges_with_less_enclosure_m3).width(0.3 + 1.dbu, projection).polygons + # via2.not_interacting(opposite9).output("m3.5", "m3.5 : min. m3 enclosure of via2 of 2 opposite edges : 0.085um") + # rule m3.pd.1, rule m3.pd.2a, rule m3.pd.2b not coded + if CU: + m3.holes().with_area((0, 0.2)).output( + "m3.7", "m3.7 : min. m3 holes area : 0.2um²" + ) + m3.sized(-2.0).sized(2.0).output( + "m3.11", "m3.11 : max. m3 width after slotting : 4.0um" + ) + # rule m3.12 not coded because inconsistent with m3.11 + # rule m3.13, m3.14, m3.14a not coded : see : https://www.klayout.de/forum/discussion/comment/6759 + + # via3 + # rule via3.3 not coded + via3.not_(m3).output("via3", "via3 : m3 must enclose all via3") + if not CU: + via3.not_(areaid_mt).edges().without_length(0.2).output( + "via3.1a", "via3.1a : minimum/maximum width of via3 : 0.2um" + ) + via3.and_(areaid_mt).not_interacting( + (via3.and_(areaid_mt).edges().without_length(0.2)).or_( + via3.and_(areaid_mt).edges().without_length(0.8) + ) + ).output( + "via3.1a", + "via3.1a : minimum/maximum width of via3 in areaid.mt: 0.2um or 0.8um", + ) + via3.isolated(0.2, euclidian).output( + "via3.2", "via3.2 : min. via3 spacing : 0.2um" + ) + m3.enclosing(via3, 0.06, euclidian).output( + "via3.4", "via3.4 : min. m3 enclosure of via3 : 0.06um" + ) + via3_edges_with_less_enclosure_m3 = m3.enclosing( + via3, 0.09, projection + ).second_edges() + opposite10 = ( + (via3.edges() - via3_edges_with_less_enclosure_m3) + .width(0.2 + 1 * deck.dbu, projection) + .polygons() + ) + via3.not_interacting(opposite10).output( + "via3.5", + "via3.5 : min. m2 enclosure of via3 of 2 opposite edges : 0.09um", + ) + if CU: + via3.edges().without_length(0.21).output( + "via3.11", "via3.11 : minimum/maximum width of via3 : 0.21um" + ) + via3.isolated(0.18, euclidian).output( + "via3.12", "via3.12 : min. via3 spacing : 0.18um" + ) + m3.enclosing(via3, 0.055, euclidian).output( + "via3.13", "via3.13 : min. m3 enclosure of via3 : 0.055um" + ) + # rule via3.14 not coded because not understandable, or not clear + # rules via3.irdrop.1, via3.irdrop.2, via3.irdrop.3, via3.irdrop.4 not coded because not understandable + + # m4 + m4.width(0.3, euclidian).output("m4.1", "m4.1 : min. m4 width : 0.3um") + + huge_m4 = m4.sized(-1.5).sized(1.5) + non_huge_m4 = m4 - huge_m4 + + non_huge_m4.space(0.3, euclidian).output( + "m4.2", "m4.2 : min. m4 spacing : 0.3um" + ) + + ( + huge_m4.separation(non_huge_m4, 0.4, euclidian) + + huge_m4.space(0.4, euclidian) + ).output("m4.5ab", "m4.5ab : min. 3um.m4 spacing m4 : 0.4um") + + m4.with_area((0, 0.24)).output("m4.4", "m4.4 : min. m4 area : 0.24um²") + via3.not_(m4).output("m4.via3", "m4.via3 : m4 must enclose via3") + if not CU: + m4.enclosing(via3, 0.065, euclidian).output( + "m4.3", "m4.3 : min. m4 enclosure of via3 : 0.065um" + ) + # m4.5 doesn't exist + # via3_edges_with_less_enclosure_m4 = m4.enclosing(via2, 0.085, projection).second_edges + # opposite9 = (via3.edges - via3_edges_with_less_enclosure_m4).width(0.3 + 1.dbu, projection).polygons + # via3.not_interacting(opposite9).output("m4.5", "m4.5 : min. m4 enclosure of via3 of 2 opposite edges : 0.085um") + # rule m4.pd.1, rule m4.pd.2a, rule m4.pd.2b not coded + if CU: + m4.holes().with_area((0, 0.2)).output( + "m4.7", "m4.7 : min. m4 holes area : 0.2um²" + ) + m4.sized(-5.0).sized(5.0).output( + "m4.11", "m4.11 : max. m4 width after slotting : 10.0um" + ) + # rule m4.12 not coded because inconsistent with m4.11 + # rule m4.13, m4.14, m4.14a not coded : see : https://www.klayout.de/forum/discussion/comment/6759 + m4.enclosing(via3, 0.06, euclidian).output( + "m4.15", "m4.15 : min. m4 enclosure of via3 : 0.06um" + ) + + # via4 + via4.edges().without_length(0.8).output( + "via4.1a", "via4.1a : minimum/maximum width of via4 : 0.8um" + ) + via4.isolated(0.8, euclidian).output( + "via4.2", "via4.2 : min. via4 spacing : 0.8um" + ) + # rule via4.3 not coded + m4.enclosing(via4, 0.19, euclidian).output( + "via4.4", "via4.4 : min. m4 enclosure of via4 : 0.19um" + ) + via4.not_(m4).output("via4", "via4 : m4 must enclose all via4") + if CU: + # rules via4.irdrop.1, via4.irdrop.2, via4.irdrop.3, via4.irdrop.4 not coded because not understandable + + # m5 + pass + m5.width(1.6, euclidian).output("m5.1", "m5.1 : min. m5 width : 1.6um") + + m5.space(1.6, euclidian).output("m5.2", "m5.2 : min. m5 spacing : 1.6um") + + via4.not_(m5).output("m5.via4", "m5.via4 : m5 must enclose via4") + m5.enclosing(via4, 0.31, euclidian).output( + "m5.3", "m4.3 : min. m5 enclosure of via4 : 0.31um" + ) + + # nsm + nsm.width(3.0, euclidian).output("nsm.1", "nsm.1 : min. nsm width : 3.0um") + nsm.isolated(4.0, euclidian).output("nsm.2", "nsm.2 : min. nsm spacing : 4.0um") + nsm.enclosing(diff, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of diff : 3.0um" + ) + nsm.enclosing(tap, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of tap : 3.0um" + ) + nsm.enclosing(poly, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of poly : 3.0um" + ) + nsm.enclosing(li, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of li : 3.0um" + ) + nsm.enclosing(m1, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of m1 : 3.0um" + ) + nsm.enclosing(m2, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of m2 : 3.0um" + ) + nsm.enclosing(m3, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of m3 : 3.0um" + ) + nsm.enclosing(m4, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of m4 : 3.0um" + ) + nsm.enclosing(m5, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of m5 : 3.0um" + ) + nsm.enclosing(cfom, 3.0, euclidian).output( + "nsm.4", "nsm.4 : min. nsm enclosure of cfom : 3.0um" + ) + if not CU: + nsm.separation(diff, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to diff : 1.0um" + ) + nsm.separation(tap, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to tap : 1.0um" + ) + nsm.separation(poly, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to poly : 1.0um" + ) + nsm.separation(li, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to li : 1.0um" + ) + nsm.separation(m1, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to m1 : 1.0um" + ) + nsm.separation(m2, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to m2 : 1.0um" + ) + nsm.separation(m3, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to m3 : 1.0um" + ) + nsm.separation(m4, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to m4 : 1.0um" + ) + nsm.separation(m5, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to m5 : 1.0um" + ) + nsm.separation(cfom, 1.0, euclidian).output( + "nsm.3", "nsm.3 : min. nsm spacing to cfom : 1.0um" + ) + + # pad + pad.isolated(1.27, euclidian).output( + "pad.2", "pad.2 : min. pad spacing : 1.27um" + ) + + if FEOL: + deck.info("FEOL section") + + # mf + mf.not_interacting(mf.edges().without_length(0.8)).output( + "mf.1", "mf.1 : minimum/maximum width of fuse : 0.8um" + ) + mf.not_interacting(mf.edges().without_length(7.2)).output( + "mf.2", "mf.2 : minimum/maximum length of fuse : 7.2um" + ) + mf.isolated(1.96, euclidian).output( + "mf.3", "mf.3 : min. fuse center spacing : 2.76um" + ) + # fuses need more clarification on fuse_shield, fuse layers ... + + # hvi + hvi.width(0.6, euclidian).output("hvi.1", "hvi.1 : min. hvi width : 0.6um") + hvi.isolated(0.7, euclidian).output( + "hvi.2", "hvi.2 : min. hvi spacing, merge if less : 0.7um" + ) + hvi.and_(tunm).output("hvi.4", "hvi.4 : hvi must not overlapp tunm") + hvi.and_(nwell).separation(nwell, 2.0, euclidian).output( + "hvnwell.8", "hvnwelli.8 : min. hvnwel spacing to nwell : 2.0" + ) + areaid_hl.not_(hvi).output("hvnwel.9", "hvnwell.9 : hvi must overlapp hvnwell") + # rule hvnell.10 not coded + diff.not_(psdm.and_(diff_rs)).and_(hvi).width(0.29, euclidian).output( + "hvdifftap.14", "hvdifftap.14 : min. diff inside hvi width : 0.29um" + ) + diff.and_(psdm.and_(diff_rs)).and_(hvi).width(0.15, euclidian).output( + "hvdifftap.14a", + "hvdifftap.14a : min. p+diff resistor inside hvi width : 0.15um", + ) + diff.and_(hvi).isolated(0.3, euclidian).output( + "hvdifftap.15a", "hvdifftap.15a : min. diff inside hvi spacing : 0.3um" + ) + diff.and_(hvi).and_(nsdm).separation( + diff.and_(hvi).and_(psdm), 0.37, euclidian + ).polygons().without_area(0).output( + "hvdifftap.15b", + "hvdifftap.15b : min. n+diff inside hvi spacing to p+diff inside hvi except abutting: 0.37um", + ) + tap.and_(hvi).edges().and_(diff).without_length(0.7).output( + "hvdifftap.16", "hvdifftap.16 : min. tap inside hvi abuttng diff : 0.7um" + ) + hvi.and_(nwell).enclosing(diff, 0.33, euclidian).output( + "hvdifftap.17", "hvdifftap.17 : min. hvnwell enclosure of p+diff : 0.33um" + ) + hvi.and_(nwell).separation(diff, 0.43, euclidian).output( + "hvdifftap.18", "hvdifftap.18 : min. hvnwell spacing to n+diff : 0.43um" + ) + hvi.and_(nwell).enclosing(tap, 0.33, euclidian).output( + "hvdifftap.19", "hvdifftap.19 : min. hvnwell enclosure of n+tap : 0.33um" + ) + hvi.and_(nwell).separation(tap, 0.43, euclidian).output( + "hvdifftap.20", "hvdifftap.20 : min. hvnwell spacing to p+tap : 0.43um" + ) + hvi.and_(diff).edges().not_(diff.edges()).output( + "hvdifftap.21", "hvdifftap.21 : diff must not straddle hvi" + ) + hvi.and_(tap).edges().not_(tap.edges()).output( + "hvdifftap.21", "hvdifftap.21 : tap must not straddle hvi" + ) + hvi.enclosing(difftap, 0.18, euclidian).output( + "hvdifftap.22", "hvdifftap.22 : min. hvi enclosure of diff or tap : 0.18um" + ) + hvi.separation(difftap, 0.18, euclidian).output( + "hvdifftap.23", "hvdifftap.23 : min. hvi spacing to diff or tap : 0.18um" + ) + hvi.and_(diff).not_(nwell).separation(nwell, 0.43, euclidian).output( + "hvdifftap.24", "hvdifftap.24 : min. hv n+diff spacing to nwell : 0.43um" + ) + diff.and_(hvi).not_(nwell).isolated(1.07, euclidian).polygons().and_( + tap + ).output( + "hvdifftap.25", + "hvdifftap.25 : min. n+diff inside hvi spacing accros p+tap : 1.07um", + ) + diff.not_(poly).edges().and_(gate.and_(hvi).edges()).space( + 0.35, euclidian + ).output("hvpoly.13", "hvpoly.13: min. hvi gate length : 0.5um") + hvi.and_(poly).edges().not_(poly.edges()).output( + "hvpoly.14", "hvpoly.14 : poly must not straddle hvi" + ) + + # hvntm + hvntm.width(0.7, euclidian).output( + "hvntm.1", "hvntm.1 : min. hvntm width : 0.7um" + ) + hvntm.isolated(0.7, euclidian).output( + "hvntm.2", "hvntm.2 : min. hvntm spacing : 0.7um" + ) + hvntm.enclosing(diff.and_(nwell).and_(hvi), 0.185, euclidian).output( + "hvntm.3", "hvntm.3 : min. hvntm enclosure of hv n+diff : 0.185um" + ) + hvntm.separation(diff.not_(nwell).not_(hvi), 0.185, euclidian).output( + "hvntm.4", "hvntm.4 : min. hvntm spacing to n+diff : 0.185um" + ) + hvntm.separation(diff.and_(nwell).not_(hvi), 0.185, euclidian).output( + "hvntm.5", "hvntm.5 : min. hvntm spacing to p+diff : 0.185um" + ) + hvntm.separation( + tap.not_(nwell).not_(hvi), 0.185, euclidian + ).polygons().without_area(0).output( + "hvntm.6a", "hvntm.6a : min. hvntm spacing to p+tap : 0.185um" + ) + hvntm.and_(areaid_ce).output( + "hvntm.9", "hvntm.9 : hvntm must not overlapp areaid.ce" + ) + + # denmos + poly.not_interacting(pwde).interacting(areaid_en).width( + 1.055, projection + ).output("denmos.1", "denmos.1 : min. de_nfet gate width : 1.055um") + diff.not_interacting(pwde).enclosing( + poly.interacting(areaid_en), 0.28, projection + ).polygons().without_area(0).output( + "denmos.2", "denmos.2 : min. de_nfet source ouside poly width : 0.28um" + ) + diff.not_interacting(pwde).and_(poly.interacting(areaid_en)).width( + 0.925, projection + ).output( + "denmos.3", "denmos.3 : min. de_nfet source inside poly width : 0.925um" + ) + diff.not_interacting(pwde).interacting(areaid_en).not_interacting(poly).width( + 0.17, euclidian + ).output("denmos.4", "denmos.4 : min. de_nfet drain width : 0.17um") + nwell.not_interacting(pwde).and_(poly.interacting(areaid_en)).width( + 0.225, projection + ).polygons().or_( + nwell.and_(poly.interacting(areaid_en)).sized(-0.1125).sized(0.1125) + ).output( + "denmos.5", "denmos.5 : min. de_nfet source inside nwell width : 0.225m" + ) + diff.not_interacting(pwde).interacting(areaid_en).not_interacting( + poly + ).separation( + diff.interacting(poly.interacting(areaid_en)), 1.585, projection + ).output( + "denmos.6", "denmos.6 : min. de_nfet source spacing to drain : 1.585um" + ) + nwell.not_interacting(pwde).and_( + poly.and_(diff).interacting(areaid_en) + ).edges().without_length(5.0, None).output( + "denmos.7", "denmos.7 : min. de_nfet channel width : 5.0um" + ) + diff.not_interacting(pwde).interacting(areaid_en).not_interacting( + poly + ).edges().without_angle(45).without_angle(135).without_angle(225).without_angle( + 315 + ).output( + "denmos.8", "denmos.8 : 90deg. not allowed for de_nfet drain" + ) + nwell.not_interacting(pwde).interacting(areaid_en).edges().without_angle( + 45 + ).without_angle(135).without_angle(225).without_angle(315).output( + "denmos.9a", "denmos.9a : 90deg. not allowed for de_nfet nwell" + ) + nwell.not_interacting(pwde).interacting(areaid_en).edges().with_angle( + 45 + ).without_length((0.607, 0.609)).output( + "denmos.9a", + "denmos.9a : 45deg. bevels of de_nfet nwell should be 0.43um from corners", + ) + nwell.not_interacting(pwde).interacting(areaid_en).edges().with_angle( + 135 + ).without_length((0.607, 0.609)).output( + "denmos.9a", + "denmos.9a : 45deg. bevels of de_nfet nwell should be 0.43um from corners", + ) + diff.not_interacting(pwde).interacting(areaid_en).not_interacting( + poly + ).edges().with_angle(45).without_length((0.7, 0.71)).output( + "denmos.9b", + "denmos.9b : 45deg. bevels of de_nfet drain should be 0.05um from corners", + ) + diff.not_interacting(pwde).interacting(areaid_en).not_interacting( + poly + ).edges().with_angle(135).without_length((0.7, 0.71)).output( + "denmos.9b", + "denmos.9b : 45deg. bevels of de_nfet drain should be 0.05um from corners", + ) + nwell.not_interacting(pwde).enclosing( + diff.interacting(areaid_en).not_interacting(poly), 0.66, euclidian + ).output( + "denmos.10", "denmos.10 : min. nwell enclosure of de_nfet drain : 0.66um" + ) + nwell.not_interacting(pwde).interacting(areaid_en).separation( + tap.not_(nwell), 0.86, euclidian + ).output("denmos.11", "denmos.11 : min. de_nfet nwell spacing to tap : 0.86um") + nwell.not_interacting(pwde).interacting(areaid_en).isolated( + 2.4, euclidian + ).output("denmos.12", "denmos.12 : min. de_nfet nwell : 2.4um") + nsdm.not_interacting(pwde).enclosing( + diff.interacting(areaid_en).interacting(poly), 0.13, euclidian + ).output( + "denmos.13", "denmos.13 : min. nsdm enclosure of de_nfet source : 0.13um" + ) + + # depmos + poly.interacting(pwde).interacting(areaid_en).width(1.05, projection).output( + "depmos.1", "depmos.1 : min. de_pfet gate width : 1.05um" + ) + diff.interacting(pwde).enclosing( + poly.interacting(areaid_en), 0.28, projection + ).polygons().without_area(0).output( + "depmos.2", "depmos.2 : min. de_pfet source ouside poly width : 0.28um" + ) + diff.interacting(pwde).and_(poly.interacting(areaid_en)).width( + 0.92, projection + ).output( + "depmos.3", "depmos.3 : min. de_pfet source inside poly width : 0.92um" + ) + diff.interacting(pwde).interacting(areaid_en).not_interacting(poly).width( + 0.17, euclidian + ).output("depmos.4", "depmos.4 : min. de_pfet drain width : 0.17um") + pwde.not_(nwell).and_(poly.interacting(areaid_en)).width( + 0.26, projection + ).polygons().or_( + pwde.not_(nwell).and_(poly.interacting(areaid_en)).sized(-0.13).sized(0.13) + ).output( + "depmos.5", "depmos.5 : min. de_pfet source inside nwell width : 0.26m" + ) + diff.interacting(pwde).interacting(areaid_en).not_interacting(poly).separation( + diff.interacting(poly.interacting(areaid_en)), 1.19, projection + ).output("depmos.6", "depmos.6 : min. de_pfet source spacing to drain : 1.19um") + nwell.interacting(pwde).and_( + poly.and_(diff).interacting(areaid_en) + ).edges().without_length(5.0, None).output( + "depmos.7", "depmos.7 : min. de_pfet channel width : 5.0um" + ) + diff.interacting(pwde).interacting(areaid_en).not_interacting( + poly + ).edges().without_angle(45).without_angle(135).without_angle(225).without_angle( + 315 + ).output( + "depmos.8", "depmos.8 : 90deg. not allowed for de_pfet drain" + ) + pwde.not_(nwell).interacting(areaid_en).edges().without_angle(45).without_angle( + 135 + ).without_angle(225).without_angle(315).output( + "depmos.9a", "depmos.9a : 90deg. not allowed for de_pfet pwell" + ) + pwde.not_(nwell).interacting(areaid_en).edges().with_angle(45).without_length( + (0.607, 0.609) + ).output( + "depmos.9a", + "depmos.9a : 45deg. bevels of de_pfet pwell should be 0.43um from corners", + ) + pwde.not_(nwell).interacting(areaid_en).edges().with_angle(135).without_length( + (0.607, 0.609) + ).output( + "depmos.9a", + "depmos.9a : 45deg. bevels of de_pfet pwell should be 0.43um from corners", + ) + diff.interacting(pwde).interacting(areaid_en).not_interacting( + poly + ).edges().with_angle(45).without_length((0.7, 0.71)).output( + "depmos.9b", + "depmos.9b : 45deg. bevels of de_pfet drain should be 0.05um from corners", + ) + diff.interacting(pwde).interacting(areaid_en).not_interacting( + poly + ).edges().with_angle(135).without_length((0.7, 0.71)).output( + "depmos.9b", + "depmos.9b : 45deg. bevels of de_pfet drain should be 0.05um from corners", + ) + nwell.interacting(pwde).separation( + diff.interacting(areaid_en).not_interacting(poly), 0.86, euclidian + ).output( + "depmos.10", "depmos.10 : min. pwell enclosure of de_pfet drain : 0.86um" + ) + pwde.not_(nwell).interacting(areaid_en).separation( + tap.and_(nwell), 0.66, euclidian + ).output("depmos.11", "depmos.11 : min. de_pfet pwell spacing to tap : 0.66um") + psdm.interacting(pwde).enclosing( + diff.interacting(areaid_en).interacting(poly), 0.13, euclidian + ).output( + "depmos.12", "depmos.12 : min. psdm enclosure of de_pfet source : 0.13um" + ) + + # extd + areaid_en.and_(difftap).edges().not_(difftap.edges()).output( + "extd.1", "extd.1 : difftap must not straddle areaid.en" + ) + difftap.interacting(areaid_en).not_(poly).with_area(0).output( + "extd.2", "extd.2 : poly must not overlapp entirely difftap in areaid.en" + ) + # rules extd.4, extd.5, extd.6, extd.7 not coded because specific to some cells + + # vhvi + # rules vhvi.vhv.1, vhvi.vhv.2, vhvi.vhv.3, vhvi.vhv.4, vhvi.vhv.5, vhvi.vhv.6 not coded + vhvi.width(0.02, euclidian).output( + "vhvi.1", "vhvi.1 : min. vhvi width : 0.02um" + ) + vhvi.and_(areaid_ce).output( + "vhvi.2", "vhvi.2 : vhvi must not overlap areaid.ce" + ) + vhvi.and_(hvi).output("vhvi.3", "vhvi.3 : vhvi must not overlap hvi") + # rules vhvi.4, vhvi.6 not coded + vhvi.and_(diff).edges().not_(diff.edges()).output( + "vhvi.5", "vhvi.5 : vhvi must not straddle diff" + ) + vhvi.and_(tap).edges().not_(tap.edges()).output( + "vhvi.5", "vhvi.5 : vhvi must not straddle tap" + ) + vhvi.and_(poly).edges().not_(poly.edges()).output( + "vhvi.7", "vhvi.7 : vhvi must not straddle poly" + ) + + nwell.and_(vhvi).separation(nwell, 2.5, euclidian).output( + "hv.nwell.1", "hv.nwell.1 : min. vhvi nwell spacing to nwell : 2.5um" + ) + diff.and_(vhvi).isolated(0.3, euclidian).output( + "hv.diff.1", "hv.diff.1 : min. vhvi diff spacing : 0.3um" + ) + nwell.interacting(diff.and_(vhvi)).separation( + diff.not_(nwell), 0.43, euclidian + ).output("hv.diff.2", "hv.diff.2 : min. vhvi nwell spacing n+diff : 0.43um") + diff.and_(vhvi).not_(nwell).separation(nwell, 0.55, euclidian).output( + "hv.diff.3a", "hv.diff.3a : min. vhvi n+diff spacing nwell : 0.55um" + ) + # rule hv.diff.3b not coded + poly.and_(vhvi).not_(diff).separation( + diff, 0.3, euclidian + ).polygons().without_area(0).output( + "hv.poly.2", "hv.poly.2 : min. vhvi poly spacing to diff : 0.3um" + ) + poly.and_(vhvi).not_(diff).separation( + nwell, 0.55, euclidian + ).polygons().without_area(0).output( + "hv.poly.3", "hv.poly.3 : min. vhvi poly spacing to nwell : 0.55um" + ) + nwell.enclosing( + poly.and_(vhvi).not_(diff), 0.3, euclidian + ).polygons().without_area(0).output( + "hv.poly.4", "hv.poly.4 : min. nwell enclosure of vhvi poly : 0.3um" + ) + # poly.and(vhvi).enclosing(diff.interacting(areaid_en), 0.16, projection).polygons.without_area(0).output("hv.poly.6", "hv.poly.6 : min. poly enclosure of hvfet gate : 0.16um") + # rule hv.poly.7 not coded + + # uhvi + uhvi.and_(diff).edges().not_(diff.edges()).output( + "uhvi.1", "uhvi.1 : diff must not straddle uhvi" + ) + uhvi.and_(tap).edges().not_(tap.edges()).output( + "uhvi.1", "uhvi.1 : tap must not straddle uhvi" + ) + uhvi.and_(poly).edges().not_(poly.edges()).output( + "uhvi.2", "uhvi.2 : poly must not straddle uhvi" + ) + pwbm.not_(uhvi).output("uhvi.3", "uhvi.3 : uhvi must not enclose pwbm") + uhvi.and_(dnwell).edges().not_(dnwell.edges()).output( + "uhvi.4", "uhvi.4 : dnwell must not straddle uhvi" + ) + areaid_en20.not_(uhvi).output( + "uhvi.5", "uhvi.5 : uhvi must not enclose areaid.en20" + ) + # dnwell.not(uhvi).output("uhvi.6", "uhvi.6 : uhvi must not enclose dnwell") + natfet.not_(uhvi).output("uhvi.7", "uhvi.7 : uhvi must not enclose natfet") + + # pwell_res + pwell_rs.width(2.65).output( + "pwres.2", "pwres.2 : min. pwell resistor width : 2.65um" + ) + pwell_rs.sized(-2.65).sized(2.65).output( + "pwres.2", "pwres.2 : max. pwell resistor width : 2.65um" + ) + pwell_rs.interacting(pwell_rs.edges().with_length(2.651, 26.499)).output( + "pwres.3", "pwres.3 : min. pwell resistor length : 26.5um" + ) + pwell_rs.interacting(pwell_rs.edges().with_length(265.0, None)).output( + "pwres.4", "pwres.4 : max. pwell resistor length : 265um" + ) + tap.interacting(pwell_rs).separation(nwell, 0.22, euclidian).output( + "pwres.5", "pwres.5 : min. pwell resistor tap spacing to nwell : 0.22um" + ) + tap.interacting(pwell_rs).and_(tap.sized(0.22).and_(nwell)).output( + "pwres.5", "pwres.5 : max. pwell resistor tap spacing to nwell : 0.22um" + ) + tap.interacting(pwell_rs).width(0.53).output( + "pwres.6", "pwres.6 : min. width of tap inside pwell resistor : 0.53um" + ) + tap.interacting(pwell_rs).sized(-0.265).sized(0.265).output( + "pwres.6", "pwres.6 : max. width of tap inside pwell resistor : 0.53um" + ) + # rules pwres.7a, pwres.7b not coded + pwell_rs.and_(diff).output( + "pwres.8", "pwres.8 : diff not allowed inside pwell resistor" + ) + pwell_rs.and_(poly).output( + "pwres.8", "pwres.8 : poly not allowed inside pwell resistor" + ) + # rules pwres.9, pwres.10 not coded + + # rf_diode + areaid_re.with_angle((0, 90)).output( + "rfdiode.1", "rfdiode.1 : non 90 degree angle areaid.re" + ) + areaid_re.not_(nwell).or_(nwell.interacting(areaid_re).not_(areaid_re)).output( + "rfdiode.2", "rfdiode.2 : areaid.re must coincide rf nwell diode" + ) + # rule rfdiode.3 not coded + + if OFFGRID: + deck.info("OFFGRID-ANGLES section") + + dnwell.ongrid(0.005).output("dnwell_OFFGRID", "x.1b : OFFGRID vertex on dnwell") + dnwell.with_angle((0, 45)).output( + "dnwell_angle", "x.3a : non 45 degree angle dnwell" + ) + nwell.ongrid(0.005).output("nwell_OFFGRID", "x.1b : OFFGRID vertex on nwell") + nwell.with_angle((0, 45)).output( + "nwell_angle", "x.3a : non 45 degree angle nwell" + ) + pwbm.ongrid(0.005).output("pwbm_OFFGRID", "x.1b : OFFGRID vertex on pwbm") + pwbm.with_angle((0, 45)).output("pwbm_angle", "x.3a : non 45 degree angle pwbm") + pwde.ongrid(0.005).output("pwde_OFFGRID", "x.1b : OFFGRID vertex on pwde") + pwde.with_angle((0, 45)).output("pwde_angle", "x.3a : non 45 degree angle pwde") + hvtp.ongrid(0.005).output("hvtp_OFFGRID", "x.1b : OFFGRID vertex on hvtp") + hvtp.with_angle((0, 45)).output("hvtp_angle", "x.3a : non 45 degree angle hvtp") + hvtr.ongrid(0.005).output("hvtr_OFFGRID", "x.1b : OFFGRID vertex on hvtr") + hvtr.with_angle((0, 45)).output("hvtr_angle", "x.3a : non 45 degree angle hvtr") + lvtn.ongrid(0.005).output("lvtn_OFFGRID", "x.1b : OFFGRID vertex on lvtn") + lvtn.with_angle((0, 45)).output("lvtn_angle", "x.3a : non 45 degree angle lvtn") + ncm.ongrid(0.005).output("ncm_OFFGRID", "x.1b : OFFGRID vertex on ncm") + ncm.with_angle((0, 45)).output("ncm_angle", "x.3a : non 45 degree angle ncm") + diff.ongrid(0.005).output("diff_OFFGRID", "x.1b : OFFGRID vertex on diff") + tap.ongrid(0.005).output("tap_OFFGRID", "x.1b : OFFGRID vertex on tap") + diff.not_(areaid_en.and_(uhvi)).with_angle((0, 90)).output( + "diff_angle", "x.2 : non 90 degree angle diff" + ) + diff.and_(areaid_en.and_(uhvi)).with_angle((0, 45)).output( + "diff_angle", "x.2c : non 45 degree angle diff" + ) + tap.not_(areaid_en.and_(uhvi)).with_angle((0, 90)).output( + "tap_angle", "x.2 : non 90 degree angle tap" + ) + tap.and_(areaid_en.and_(uhvi)).with_angle((0, 45)).output( + "tap_angle", "x.2c : non 45 degree angle tap" + ) + tunm.ongrid(0.005).output("tunm_OFFGRID", "x.1b : OFFGRID vertex on tunm") + tunm.with_angle((0, 45)).output("tunm_angle", "x.3a : non 45 degree angle tunm") + poly.ongrid(0.005).output("poly_OFFGRID", "x.1b : OFFGRID vertex on poly") + poly.with_angle((0, 90)).output("poly_angle", "x.2 : non 90 degree angle poly") + rpm.ongrid(0.005).output("rpm_OFFGRID", "x.1b : OFFGRID vertex on rpm") + rpm.with_angle((0, 45)).output("rpm_angle", "x.3a : non 45 degree angle rpm") + npc.ongrid(0.005).output("npc_OFFGRID", "x.1b : OFFGRID vertex on npc") + npc.with_angle((0, 45)).output("npc_angle", "x.3a : non 45 degree angle npc") + nsdm.ongrid(0.005).output("nsdm_OFFGRID", "x.1b : OFFGRID vertex on nsdm") + nsdm.with_angle((0, 45)).output("nsdm_angle", "x.3a : non 45 degree angle nsdm") + psdm.ongrid(0.005).output("psdm_OFFGRID", "x.1b : OFFGRID vertex on psdm") + psdm.with_angle((0, 45)).output("psdm_angle", "x.3a : non 45 degree angle psdm") + licon.ongrid(0.005).output("licon_OFFGRID", "x.1b : OFFGRID vertex on licon") + licon.with_angle((0, 90)).output( + "licon_angle", "x.2 : non 90 degree angle licon" + ) + li.ongrid(0.005).output("li_OFFGRID", "x.1b : OFFGRID vertex on li") + li.with_angle((0, 45)).output("li_angle", "x.3a : non 45 degree angle li") + mcon.ongrid(0.005).output("ct_OFFGRID", "x.1b : OFFGRID vertex on mcon") + mcon.with_angle((0, 90)).output("ct_angle", "x.2 : non 90 degree angle mcon") + vpp.ongrid(0.005).output("vpp_OFFGRID", "x.1b : OFFGRID vertex on vpp") + vpp.with_angle((0, 45)).output("vpp_angle", "x.3a : non 45 degree angle vpp") + m1.ongrid(0.005).output("m1_OFFGRID", "x.1b : OFFGRID vertex on m1") + m1.with_angle((0, 45)).output("m1_angle", "x.3a : non 45 degree angle m1") + via.ongrid(0.005).output("via_OFFGRID", "x.1b : OFFGRID vertex on via") + via.with_angle((0, 90)).output("via_angle", "x.2 : non 90 degree angle via") + m2.ongrid(0.005).output("m2_OFFGRID", "x.1b : OFFGRID vertex on m2") + m2.with_angle((0, 45)).output("m2_angle", "x.3a : non 45 degree angle m2") + via2.ongrid(0.005).output("via2_OFFGRID", "x.1b : OFFGRID vertex on via2") + via2.with_angle((0, 90)).output("via2_angle", "x.2 : non 90 degree angle via2") + m3.ongrid(0.005).output("m3_OFFGRID", "x.1b : OFFGRID vertex on m3") + m3.with_angle((0, 45)).output("m3_angle", "x.3a : non 45 degree angle m3") + via3.ongrid(0.005).output("via3_OFFGRID", "x.1b : OFFGRID vertex on via3") + via3.with_angle((0, 90)).output("via3_angle", "x.2 : non 90 degree angle via3") + nsm.ongrid(0.005).output("nsm_OFFGRID", "x.1b : OFFGRID vertex on nsm") + nsm.with_angle((0, 45)).output("nsm_angle", "x.3a : non 45 degree angle nsm") + m4.ongrid(0.005).output("m4_OFFGRID", "x.1b : OFFGRID vertex on m4") + m4.with_angle((0, 45)).output("m4_angle", "x.3a : non 45 degree angle m4") + via4.ongrid(0.005).output("via4_OFFGRID", "x.1b : OFFGRID vertex on via4") + via4.with_angle((0, 90)).output("via4_angle", "x.2 : non 90 degree angle via4") + m5.ongrid(0.005).output("m5_OFFGRID", "x.1b : OFFGRID vertex on m5") + m5.with_angle((0, 45)).output("m5_angle", "x.3a : non 45 degree angle m5") + pad.ongrid(0.005).output("pad_OFFGRID", "x.1b : OFFGRID vertex on pad") + pad.with_angle((0, 45)).output("pad_angle", "x.3a : non 45 degree angle pad") + mf.ongrid(0.005).output("mf_OFFGRID", "x.1b : OFFGRID vertex on mf") + mf.with_angle((0, 90)).output("mf_angle", "x.2 : non 90 degree angle mf") + hvi.ongrid(0.005).output("hvi_OFFGRID", "x.1b : OFFGRID vertex on hvi") + hvi.with_angle((0, 45)).output("hvi_angle", "x.3a : non 45 degree angle hvi") + hvntm.ongrid(0.005).output("hvntm_OFFGRID", "x.1b : OFFGRID vertex on hvntm") + hvntm.with_angle((0, 45)).output( + "hvntm_angle", "x.3a : non 45 degree angle hvntm" + ) + vhvi.ongrid(0.005).output("vhvi_OFFGRID", "x.1b : OFFGRID vertex on vhvi") + vhvi.with_angle((0, 45)).output("vhvi_angle", "x.3a : non 45 degree angle vhvi") + uhvi.ongrid(0.005).output("uhvi_OFFGRID", "x.1b : OFFGRID vertex on uhvi") + uhvi.with_angle((0, 45)).output("uhvi_angle", "x.3a : non 45 degree angle uhvi") + pwell_rs.ongrid(0.005).output( + "pwell_rs_OFFGRID", "x.1b : OFFGRID vertex on pwell_rs" + ) + pwell_rs.with_angle((0, 45)).output( + "pwell_rs_angle", "x.3a : non 45 degree angle pwell_rs" + ) + areaid_re.ongrid(0.005).output( + "areaid_re_OFFGRID", "x.1b : OFFGRID vertex on areaid.re" + ) diff --git a/flow/util/klayout_drc.py b/flow/util/klayout_drc.py index 691db18ccc..77635eb8f8 100644 --- a/flow/util/klayout_drc.py +++ b/flow/util/klayout_drc.py @@ -315,7 +315,7 @@ def antenna_check(self, gate, metal, ratio, *diodes): ) def info(self, msg): - print("%7.1fs %s" % (time.time() - self.t0, msg)) + print("%7.1fs %s" % (time.time() - self.t0, msg), flush=True) def output(self, data, name, description=None): cat = self.categories.get(name) @@ -702,6 +702,8 @@ def parse_args(argv): def load_deck(path): + # No __pycache__ next to the deck, in the platform directory + sys.dont_write_bytecode = True spec = importlib.util.spec_from_file_location("klayout_drc_deck", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) From 712222546079995cd1360d2a4a281a37862f8e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 08:42:37 +0100 Subject: [PATCH 05/11] sky130hd: LVS deck in Python MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, make lvs on sky130hd needed the KLayout application to run sky130hd.lylvs. Now it runs sky130hd_lvs.py on the klayout Python module. flow/util/klayout_lvs.py is what the LVS language adds to the DRC language (_lvs_netter.rb), for what this deck uses: mos4 device extraction with the layers registered in sorted terminal order, the CDL schematic, connect_global, device scaling, split_gates and tolerance applied once the netlist is extracted, same_circuits and equivalent_pins applied to each NetlistComparer when it is made, align, compare, and the short-format .lvsdb. The deck's schematic and netlist edits (0-ohm ties in CONB_1, the stack merges, the VNB/VSS tie) are the Ruby functions in Python; nets are keyed by name, as the Ruby deck does, since the binding hands back a new wrapper on each lookup. The Ruby deck wrote the extracted netlist next to RBA::CellView::active, which only the application has; the Python deck writes the .lvsdb only, which holds the extracted netlist. Checked against the Ruby deck run by the KLayout 0.30.0 application on the same sky130hd/gcd GDS and CDL, Python on klayout 0.30.13, comparing the cross references in the two .lvsdb files: clean GDS: match, both: 59 circuits, 496 devices, 1119 nets, 931 pins, 451 subcircuits matched, both shorted GDS: no match, both (a met1 bar across the core): 1 circuit, 10 nets and 28 subcircuits mismatched, the rest matched, the same counts in both runtime: 2.6 s Ruby, 0.8 s Python The device abstracts are the same set in both .lvsdb files, in a different order. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- flow/Makefile | 11 +- flow/platforms/sky130hd/config.mk | 2 +- flow/platforms/sky130hd/lvs/sky130hd.lylvs | 572 -------------------- flow/platforms/sky130hd/lvs/sky130hd_lvs.py | 436 +++++++++++++++ flow/test/test_klayout_sh.py | 12 +- flow/util/klayout_lvs.py | 209 +++++++ 6 files changed, 661 insertions(+), 581 deletions(-) delete mode 100644 flow/platforms/sky130hd/lvs/sky130hd.lylvs create mode 100644 flow/platforms/sky130hd/lvs/sky130hd_lvs.py create mode 100644 flow/util/klayout_lvs.py diff --git a/flow/Makefile b/flow/Makefile index 3c0ed8e58d..566f38c881 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -831,14 +831,21 @@ $(OBJECTS_DIR)/6_final_concat.cdl: $(RESULTS_DIR)/6_final.cdl $(CDL_FILE) .PHONY: lvs lvs: $(RESULTS_DIR)/6_lvs.lvsdb -$(RESULTS_DIR)/6_lvs.lvsdb: $(GDS_FINAL_FILE) $(KLAYOUT_LVS_FILE) $(OBJECTS_DIR)/6_final_concat.cdl | check-klayout +$(RESULTS_DIR)/6_lvs.lvsdb: $(GDS_FINAL_FILE) $(KLAYOUT_LVS_FILE) $(OBJECTS_DIR)/6_final_concat.cdl $(if $(filter %.py,$(KLAYOUT_LVS_FILE)),$(UTILS_DIR)/klayout_lvs.py $(UTILS_DIR)/klayout_drc.py,| check-klayout) ifneq ($(KLAYOUT_LVS_FILE),) $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_lvs.log) --tee -- \ + $(if $(filter %.py,$(KLAYOUT_LVS_FILE)), \ + $(PYTHON_EXE) $(UTILS_DIR)/klayout_lvs.py \ + --deck=$(KLAYOUT_LVS_FILE) \ + --in-gds=$< \ + --cdl-file=$(abspath $(OBJECTS_DIR)/6_final_concat.cdl) \ + --top-cell=$(DESIGN_NAME) \ + --report-file=$(abspath $@), \ $(SCRIPTS_DIR)/klayout.sh -b -rd in_gds="$<" \ -rd cdl_file=$(abspath $(OBJECTS_DIR)/6_final_concat.cdl) \ -rd top_cell="$(DESIGN_NAME)" \ -rd report_file=$(abspath $@) \ - -r $(KLAYOUT_LVS_FILE) + -r $(KLAYOUT_LVS_FILE)) else echo "LVS not supported on this platform" > $@ endif diff --git a/flow/platforms/sky130hd/config.mk b/flow/platforms/sky130hd/config.mk index b17ba06ef3..82927986e6 100644 --- a/flow/platforms/sky130hd/config.mk +++ b/flow/platforms/sky130hd/config.mk @@ -151,5 +151,5 @@ export KLAYOUT_DRC_FILE ?= $(PLATFORM_DIR)/drc/$(PLATFORM)_drc.py #LVS Check export CDL_FILE = $(PLATFORM_DIR)/cdl/$(PLATFORM).cdl -export KLAYOUT_LVS_FILE = $(PLATFORM_DIR)/lvs/$(PLATFORM).lylvs +export KLAYOUT_LVS_FILE ?= $(PLATFORM_DIR)/lvs/$(PLATFORM)_lvs.py export REMOVE_CELLS_FOR_LEC = sky130_fd_sc_hd__tapvpwrvgnd* diff --git a/flow/platforms/sky130hd/lvs/sky130hd.lylvs b/flow/platforms/sky130hd/lvs/sky130hd.lylvs deleted file mode 100644 index 12eb8bf51a..0000000000 --- a/flow/platforms/sky130hd/lvs/sky130hd.lylvs +++ /dev/null @@ -1,572 +0,0 @@ - - - - - lvs - - - - false - false - - true - lvs_scripts - tools_menu.lvs.end - dsl - lvs-dsl-xml - # -# Extraction for SKY130 -# -############################ - -# optionnal for a batch launch : klayout -b -rd in_gds=my_layout.gds -rd report_file=my_report.lyrdb -rd cdl_file=reference_netlist.cir -rd target_netlist=extracted_netlist.cir -r sky130.lylvs -if $in_gds - source($in_gds) -end - -if $report_file - report_lvs($report_file) -else - report_lvs("lvs_report.lvsdb") -end - -if $cdl_file -#reference netlist - schematic($cdl_file) -else - schematic("sky130hd.cdl") -end - -# true: use net names instead of numbers -# false: use numbers for nets -spice_with_net_names = true - -# true: put in comments with details -# false: no comments -spice_with_comments = false - -if $target_netlist - target_netlist($target_netlist) -else - # target_netlist("netlist.cir", write_spice(spice_with_net_names, spice_with_comments), "The netlist comment goes here.") - target_netlist(File.join(File.dirname(RBA::CellView::active.filename), source.cell_name+"_extracted.cir"), write_spice(spice_with_net_names, spice_with_comments), "Extracted by KLayout on : #{Time.now.strftime("%d/%m/%Y %H:%M")}") -end - -# Hierarchical mode -deep -# Use 4 CPU cores -threads(4) -# Print details -verbose(true) - - -# layers definitions -######################## -BOUND = polygons(235, 4) -DNWELL = polygons(64, 18) -PWRES = polygons(64, 13) -NWELL = polygons(64, 20) -NWELLTXT = input(64, 5) -NWELLPIN = polygons(64, 16) -SUBTXT = input(122, 5) -SUBPIN = input(64, 59) -DIFF = polygons(65, 20) -TAP = polygons(65, 44) -PSDM = polygons(94, 20) -NSDM = polygons(93, 44) -LVTN = polygons(125, 44) -HVTR = polygons(18, 20) -HVTP = polygons(78, 44) -SONOS = polygons(80, 20) -COREID = polygons(81, 2) -STDCELL = polygons(81, 4) -NPNID = polygons(82, 20) -PNPID = polygons(82, 44) -RPM = polygons(86, 20) -URPM = polygons(79, 20) -LDNTM = polygons(11, 44) -HVNTM = polygons(125, 20) -POLY = polygons(66, 20) -POLYTXT = input(66, 5) -POLYPIN = polygons(66, 16) -HVI = polygons(75, 20) -LICON = polygons(66, 44) -NPC = polygons(95, 20) -DIFFRES = polygons(65, 13) -POLYRES = polygons(66, 13) -POLYSHO = polygons(66, 15) -DIODE = polygons(81, 23) -LI = polygons(67, 20) -LITXT = input(67, 5) -LIPIN = polygons(67, 16) -LIRES = polygons(67, 13) -MCON = polygons(67, 44) -MET1 = polygons(68, 20) -MET1TXT = input(68, 5) -MET1PIN = polygons(68, 16) -MET1RES = polygons(68, 13) -VIA1 = polygons(68, 44) -MET2 = polygons(69, 20) -MET2TXT = input(69, 5) -MET2PIN = polygons(69, 16) -MET2RES = polygons(69, 13) -VIA2 = polygons(69, 44) -MET3 = polygons(70, 20) -MET3TXT = input(70, 5) -MET3PIN = polygons(70, 16) -MET3RES = polygons(70, 13) -VIA3 = polygons(70, 44) -MET4 = polygons(71, 20) -MET4TXT = input(71, 5) -MET4PIN = polygons(71, 16) -MET4RES = polygons(71, 13) -VIA4 = polygons(71, 44) -MET5 = polygons(72, 20) -MET5TXT = input(72, 5) -MET5PIN = polygons(72, 16) -MET5RES = polygons(72, 13) -RDL = polygons(74, 20) -RDLTXT = input(74, 5) -RDLPIN = polygons(74, 16) -GLASS = polygons(76, 20) -CAPM = polygons(89, 44) -CAPM2 = polygons(97, 44) -LOWTAPD = polygons(81, 14) -FILLOBSM1 = polygons(62, 24) -FILLOBSM2 = polygons(105, 52) -FILLOBSM3 = polygons(107, 24) -FILLOBSM4 = polygons(112, 4) -NCM = polygons(92, 44) - -# Bulk layer for terminal provisioning -SUB = polygons(236, 0) -# SUB = polygon_layer - -# Computed layers -PDIFF = DIFF & NWELL & PSDM -NTAP = TAP & NWELL & NSDM -PGATE = PDIFF & POLY -PSD = PDIFF - PGATE -STD_PGATE = PGATE - HVTP - NCM - HVI -HVT_PGATE = PGATE & HVTP - NCM - HVI -HV5_PGATE = PGATE - HVTP - NCM & HVI - -NDIFF = DIFF - NWELL & NSDM -PTAP = TAP - NWELL & PSDM -NGATE = NDIFF & POLY -NSD = NDIFF - NGATE -STD_NGATE = NGATE - NCM - LVTN - HVI -LVT_NGATE = NGATE - NCM & LVTN - HVI -HV5_NGATE = NGATE - NCM - LVTN & HVI -HV5NA_NGATE = NGATE - NCM & LVTN & HVI - -# drawing to physical -device_scaling(1000000) - -# PMOS transistor device extraction -extract_devices(mos4("pfet_01v8"), { "SD" => PSD, "G" => STD_PGATE, "tS" => PSD, "tD" => PSD, "tG" => POLY, "W" => NWELL }) -extract_devices(mos4("pfet_01v8_hvt"), { "SD" => PSD, "G" => HVT_PGATE, "tS" => PSD, "tD" => PSD, "tG" => POLY, "W" => NWELL }) -extract_devices(mos4("pfet_g5v0d10v5"), { "SD" => PSD, "G" => HV5_PGATE, "tS" => PSD, "tD" => PSD, "tG" => POLY, "W" => NWELL }) - -# NMOS transistor device extraction -extract_devices(mos4("nfet_01v8"), { "SD" => NSD, "G" => STD_NGATE, "tS" => NSD, "tD" => NSD, "tG" => POLY, "W" => SUB }) -extract_devices(mos4("nfet_01v8_lvt"), { "SD" => NSD, "G" => LVT_NGATE, "tS" => NSD, "tD" => NSD, "tG" => POLY, "W" => SUB }) -extract_devices(mos4("nfet_g5v0d10v5"), { "SD" => NSD, "G" => HV5_NGATE, "tS" => NSD, "tD" => NSD, "tG" => POLY, "W" => SUB }) -extract_devices(mos4("nfet_01v8_nvt"), { "SD" => NSD, "G" => HV5NA_NGATE, "tS" => NSD, "tD" => NSD, "tG" => POLY, "W" => SUB }) - - -# Define connectivity for netlist extraction - -# Inter-layer -connect(SUB, PTAP) -connect(NWELL, NTAP) -connect(LICON, PTAP) -connect(LICON, NTAP) -connect(PSD, LICON) -connect(NSD, LICON) -connect(POLY, LICON) -connect(LICON, LI) -connect(LI, MCON) -connect(MCON, MET1) -connect(MET1,VIA1) -connect(VIA1, MET2) -connect(MET2, VIA2) -connect(VIA2, MET3) -connect(MET3, VIA3) -connect(VIA3, MET4) -connect(MET4, VIA4) -connect(VIA4, MET5) -# Attaching labels -connect(SUB, SUBTXT) -connect(SUB, SUBPIN) -connect(NWELL, NWELLTXT) -connect(POLY, POLYTXT) -connect(LI, LITXT) -connect(MET1, MET1TXT) -connect(MET2, MET2TXT) -connect(MET3, MET3TXT) -connect(MET4, MET4TXT) -connect(MET5, MET5TXT) - -# Global -connect_global(SUB, "VNB") - -# Merge 0-ohm tie resistors in the schematic into direct net shorts, -# so their structure matches the physical layout (which has no discrete -# device for these ties, just a metal/contact short). -schematic.each_circuit do |c| - next unless c.name == "SKY130_FD_SC_HD__CONB_1" - to_remove = [] - c.each_device do |d| - dc = d.device_class - if dc.name == "RES" && d.parameter("R") == 0.0 - na = d.net_for_terminal(dc.terminal_id("A")) - nb = d.net_for_terminal(dc.terminal_id("B")) - c.join_nets(na, nb) if na && nb && na.name != nb.name - to_remove << d - end - end - to_remove.each { |d| c.remove_device(d) } -end - -same_circuits("sky130_fd_sc_hd__conb_1", "SKY130_FD_SC_HD__CONB_1") - -# -# Required to match transistors properly -# -# NOTE: real GDS-extracted layout often ties 3+ transistor fingers to a single -# shared internal diffusion node (interdigitated/folded layout), whereas the -# idealized schematic CDL always has exactly 2 terminals per internal fold -# node. The original version of this function required terms.size == 2, -# which silently skipped any internal net with more fanout than that, -# leaving some fingers permanently unmerged no matter how many convergence -# iterations ran. -# -# This version builds a signature for EVERY qualifying terminal on a net -# (any count), then requires the ENTIRE sorted multiset of a net's terminal -# signatures to match another net's before considering them the same -# electrical node and joining them. This generalizes cleanly to N-terminal -# nodes while still refusing to conflate two nets that only partially -# overlap in connectivity (e.g. two distinct internal series junctions in a -# 3-stage stack) -- an earlier, more permissive per-terminal-match version -# caused exactly that kind of incorrect over-merge. -# -# Signature keys go through net_key() (see above), not raw .name, because -# on the layout side unnamed internal nets all key as "" until something -# merges/labels them -- two genuinely different anonymous nets can -# otherwise collide on that empty string and get incorrectly folded -# together (observed: two separate 2-terminal PFET fold nodes merged into -# one bogus 4-terminal net purely because their neighboring nets were -# still unnamed at merge time). -# -# -# Signature key for a net. On the SCHEMATIC side every net already has a -# real, stable name pulled from the CDL, so using .name directly is safe. -# On the LAYOUT side, internal nets extracted from raw geometry are -# anonymous ("" name) until something merges/labels them -- which means -# two genuinely DIFFERENT unnamed internal nets can both key as "" and -# collide in the signature below, causing a false-positive merge between -# structurally distinct nodes (observed: two separate 2-terminal PFET -# fold nodes getting joined into one bogus 4-terminal net, purely because -# their neighboring "opposite" and "gate" nets were both still unnamed at -# merge time). Falling back to Ruby object identity for unnamed nets -# guarantees distinct anonymous nets never share a key. -# -# -# IMPORTANT: neither net.object_id NOR RBA::Net#== can be trusted as a -# stable identity for the same underlying net across different retrieval -# paths. Direct signature tracing proved BOTH broken in turn: -# 1) object_id: a net's own terminal reported one object_id, while the -# SAME physical net, reached via a neighboring terminal's -# net_for_terminal(...) lookup, reported a DIFFERENT object_id. -# 2) ==: even after switching to an ==-based cache, two independently -# -obtained wrapper references to the IDENTICAL physical net (PNDA, -# looked up separately from its two different neighbors) did not -# compare as == to each other, so each got assigned a distinct label -# instead of being recognized as the same net. -# KLayout's RBA bindings evidently hand back a freshly-constructed Ruby -# wrapper object on each indirect lookup, and neither Ruby-level identity -# nor the RBA-level equality operator sees through that to the underlying -# net for this API/version. -# -# The fix that actually works: give every unnamed net a real, PERSISTENT -# name up front, via net.name=. Naming is stored as state on the -# underlying net object itself (that's what makes a net's name durable at -# all) -- so every subsequent lookup of that same net, through WHATEVER -# retrieval path, reports the identical name string back. This sidesteps -# wrapper identity entirely and puts anonymous nets on the same footing -# as the schematic side, where real persisted names never had this -# problem in the first place. -# -def merge_parallel_series_stacks(circuit, device_class_names) - synth_counter = 0 - circuit.each_net do |n| - if n.name.nil? || n.name.empty? - synth_counter += 1 - n.name = "synth_#{synth_counter}" - end - end - - net_key = lambda do |net| - next "unconnected" if net.nil? - net.name - end - - chains = [] - circuit.each_net do |net| - next if net.each_pin.any? || net.each_subcircuit_pin.any? - - # Build a signature for every qualifying S/D terminal on this net (any - # count -- not just exactly 2, since real GDS-extracted layout can tie - # 3+ fingers to one shared diffusion node). Two nets are only considered - # interchangeable if their ENTIRE terminal signature multisets match -- - # not just one terminal in common -- otherwise nodes with genuinely - # different roles in the stack (e.g. two separate internal series - # junctions) can get incorrectly unioned just because one terminal - # happens to coincide. - # Hard guard: a genuine multi-finger diffusion fold node is purely an - # internal S/D connection and NEVER also drives a gate. A net that does - # double duty as both a drain/source AND a gate input elsewhere (e.g. a - # cross-coupled latch's storage node, which drives the gates of the - # opposite inverter) is a real, electrically distinct circuit node -- - # not a layout-folding artifact -- even if its S/D signature happens to - # look symmetric with another node. Skip any net where any device (of - # ANY class, not just the merge-target classes) has a gate terminal here. - has_gate_terminal = net.each_terminal.any? do |t| - dc = t.device.device_class - begin - t.terminal_id == dc.terminal_id("G") - rescue - false - end - end - next if has_gate_terminal - - term_sigs = [] - net.each_terminal.each do |t| - d = t.device - dc = d.device_class - next unless device_class_names.any? { |n| n.downcase == dc.name.downcase } - - tid_s = dc.terminal_id("S") - tid_g = dc.terminal_id("G") - tid_d = dc.terminal_id("D") - tid_b = dc.terminal_id("B") - - role = t.terminal_id - next unless [tid_s, tid_d].include?(role) - opp = (role == tid_s) ? tid_d : tid_s - - # NOTE: role is intentionally NOT included in the signature. MOSFETs - # are physically symmetric devices -- S and D are interchangeable for - # connectivity purposes. KLayout's raw device extraction can assign - # the S/D role oppositely between two structurally-identical devices - # that are simple mirror images of each other in the layout (e.g. two - # parallel drive-strength fingers of the same gate). Including role - # as a literal signature field caused exactly that: two electrically - # identical fold-node terminals differed only in role (S vs D) and so - # never matched, leaving genuinely parallel finger-doubled legs - # unmerged (observed: a two-leg B1-C1 pull-up in a211oi_4 where one - # leg's C1 PFET had opposite=Y via its D terminal and the other leg's - # via its S terminal -- same device topology, different raw role). - term_sigs << [net_key.call(d.net_for_terminal(opp)), net_key.call(d.net_for_terminal(tid_g)), net_key.call(d.net_for_terminal(tid_b)), dc.name.downcase] - end - next if term_sigs.size < 2 - - whole_net_sig = term_sigs.sort - chains << [whole_net_sig, net] - end - - groups = {} - chains.each { |sig, net| (groups[sig] ||= []) << net } - - # A net can appear only once here (one whole-net signature per net now, - # not one per terminal), but joining still mutates the netlist as we go, - # so guard against reusing a net object already consumed by an earlier - # join in this same pass -- reusing a stale reference corrupts KLayout's - # internal net registry and crashes the SPICE writer later. Anything left - # over is picked up cleanly on the next converge_merge iteration. - joined = {} - groups.each_value do |nets_in_group| - uniq_nets = nets_in_group.uniq.reject { |n| joined[n.object_id] } - next if uniq_nets.size < 2 - keep = uniq_nets[0] - uniq_nets.each { |n| joined[n.object_id] = true } - uniq_nets[1..-1].each { |other| circuit.join_nets(keep, other) } - end -end - -# -# Iteratively merge parallel/series stacks until the device count -# stops shrinking (a single pass can leave multi-finger devices -# partially folded, e.g. sky130 cells where a "wide" transistor is -# laid out as several minimum-width fingers in parallel). -# -def converge_merge(circuit, nmos_classes, pmos_classes, max_iters = 10) - prev_count = -1 - max_iters.times do - merge_parallel_series_stacks(circuit, nmos_classes) - merge_parallel_series_stacks(circuit, pmos_classes) - circuit.combine_devices - count = circuit.each_device.to_a.size - return if count == prev_count - prev_count = count - end - puts "WARNING: converge_merge did not stabilize for #{circuit.name} after #{max_iters} iterations" -end - -# -# Apply to all transistor types -# -nmos_classes = ["nfet_01v8", "nfet_01v8_lvt", "nfet_g5v0d10v5", "nfet_01v8_nvt"] -pmos_classes = ["pfet_01v8", "pfet_01v8_hvt", "pfet_g5v0d10v5"] - -schematic.each_circuit do |c| - converge_merge(c, nmos_classes, pmos_classes) -end - -# Actually performs the extraction -netlist # ... not really required - -# Keep diode in both netlist and schematic, so it matches -c1 = netlist.circuit_by_name("sky130_fd_sc_hd__conb_1") -c1.dont_purge = true if c1 -c2 = schematic.circuit_by_name("SKY130_FD_SC_HD__CONB_1") -c2.dont_purge = true if c2 - -split_gates("nfet_01v8") -split_gates("nfet_01v8_lvt") -split_gates("nfet_g5v0d10v5") -split_gates("nfet_01v8_nvt") -split_gates("pfet_01v8") -split_gates("pfet_01v8_hvt") -split_gates("pfet_g5v0d10v5") - -# -# Same multi-finger folding applies to the layout-extracted netlist. -# Empirically this must run AFTER split_gates, not before: running it -# earlier (against the raw, still-undecomposed extracted device -# representation) produced MORE fragmented results, not fewer. -# -netlist.each_circuit do |c| - converge_merge(c, nmos_classes, pmos_classes) -end - - -# Ties VNB and VSS across all cells. -# -# NOTE: net_by_name("VSS") does an EXACT match. But KLayout's own -# extraction can already merge the ground net together with tied-off -# signal pins before this code runs -- e.g. constant-0 outputs like -# alert_major_o/alert_minor_o/data_addr_o[0:1]/instr_addr_o[0:1] that RTL -# wires directly to ground for a disabled feature or unused bit. Once -# merged, KLayout renames the net to a concatenated string like -# "VSS,data_addr_o[0],instr_addr_o[0],..." (the same convention that -# produced "VNB,VSS" earlier), which no longer equals "VSS" exactly -- -# so net_by_name("VSS") silently returns nil, the VNB/VSS join never -# fires, and VNB is left stranded on its own to fail comparison (observed -# at the ibex_core top level: VNB unmatched by itself, VSS bundled into -# a separate large tied-off-signal net). Find VSS by checking each net's -# comma-separated name components instead of requiring an exact match. -# -def find_net_by_name_component(circuit, component) - circuit.each_net.find do |n| - nm = n.name - nm && nm.split(",").include?(component) - end -end - -netlist.each_circuit do |c| - next unless c.name == $top_cell - n_vnb = find_net_by_name_component(c, "VNB") - n_vss = find_net_by_name_component(c, "VSS") - c.join_nets(n_vnb, n_vss) if n_vnb && n_vss && n_vnb.name != n_vss.name -end - -# Flatten cells which are present in one netlist only -align -# SIMPLIFICATION of the netlist -#netlist.make_top_level_pins -#netlist.combine_devices -#netlist.purge -#netlist.purge_nets -netlist.simplify -schematic.simplify - -# Tolerances for the devices extracted parameters -# tolerance(device_class_name, parameter_name [, :absolute => absolute_tolerance] [, :relative => relative_tolerance]) -tolerance("pfet_01v8", "W", :absolute => 1.nm, :relative => 0.001) -tolerance("pfet_01v8", "L", :absolute => 1.nm, :relative => 0.001) -tolerance("pfet_01v8_hvt", "W", :absolute => 1.nm, :relative => 0.001) -tolerance("pfet_01v8_hvt", "L", :absolute => 1.nm, :relative => 0.001) -tolerance("nfet_01v8", "W", :absolute => 1.nm, :relative => 0.001) -tolerance("nfet_01v8", "L", :absolute => 1.nm, :relative => 0.001) -tolerance("nfet_01v8_lvt", "W", :absolute => 1.nm, :relative => 0.001) -tolerance("nfet_01v8_lvt", "L", :absolute => 1.nm, :relative => 0.001) - -# NangateOpenCellLibrary Digital gates input equivalence : -equivalent_pins("*AND2_1", "A", "B") -equivalent_pins("*AND2_2", "A", "B") -equivalent_pins("*AND2_4", "A", "B") -equivalent_pins("*AND3_1", "A", "B", "C") -equivalent_pins("*AND3_2", "A", "B", "C") -equivalent_pins("*AND3_4", "A", "B", "C") -equivalent_pins("*AND4_1", "A", "B", "C", "D") -equivalent_pins("*AND4_2", "A", "B", "C", "D") -equivalent_pins("*AND4_4", "A", "B", "C", "D") -equivalent_pins("*A21OI_1", "A1", "A2") -equivalent_pins("*A21OI_2", "A1", "A2") -equivalent_pins("*A21OI_4", "A1", "A2") -equivalent_pins("*A211OI_1", "A1", "A2") -equivalent_pins("*A211OI_1", "B1", "C1") -equivalent_pins("*A211OI_2", "A1", "A2") -equivalent_pins("*A211OI_2", "B1", "C1") -equivalent_pins("*A211OI_4", "A1", "A2") -equivalent_pins("*A211OI_4", "B1", "C1") -equivalent_pins("*A221OI_1", "A1", "A2") -equivalent_pins("*A221OI_1", "B1", "B2") -equivalent_pins("*A22OI_1", "A1", "A2") -equivalent_pins("*A22OI_1", "B1", "B2") -equivalent_pins("*NAND2_1", "A", "B") -equivalent_pins("*NAND2_2", "A", "B") -equivalent_pins("*NAND2_4", "A", "B") -equivalent_pins("*NAND3_1", "A", "B", "C") -equivalent_pins("*NAND3_2", "A", "B", "C") -equivalent_pins("*NAND3_4", "A", "B", "C") -equivalent_pins("*NAND4_X1", "A", "B", "C", "D") -equivalent_pins("*NAND4_2", "A", "B", "C", "D") -equivalent_pins("*NAND4_4", "A", "B", "C", "D") - -equivalent_pins("*OR2_1", "A", "B") -equivalent_pins("*OR2_2", "A", "B") -equivalent_pins("*OR2_4", "A", "B") -equivalent_pins("*OR3_1", "A", "B", "C") -equivalent_pins("*OR3_2", "A", "B", "C") -equivalent_pins("*OR3_4", "A", "B", "C") -equivalent_pins("*OR4_1", "A", "B", "C", "D") -equivalent_pins("*OR4_2", "A", "B", "C", "D") -equivalent_pins("*OR4_4", "A", "B", "C", "D") -equivalent_pins("*NOR2_1", "A", "B") -equivalent_pins("*NOR2_2", "A", "B") -equivalent_pins("*NOR2_4", "A", "B") -equivalent_pins("*NOR3_1", "A", "B", "C") -equivalent_pins("*NOR3_2", "A", "B", "C") -equivalent_pins("*NOR3_4", "A", "B", "C") -equivalent_pins("*NOR4_1", "A", "B", "C", "D") -equivalent_pins("*NOR4_2", "A", "B", "C", "D") -equivalent_pins("*NOR4_4", "A", "B", "C", "D") -equivalent_pins("*XOR2_1", "A", "B") -equivalent_pins("*XOR2_2", "A", "B") -equivalent_pins("*XNOR2_1", "A", "B") -equivalent_pins("*XNOR2_2", "A", "B") - -#max_res(1000000) -#min_caps(1e-15) - -if ! compare - #raise "ERROR : Netlists don't match" - puts "ERROR : Netlists don't match" -else - puts "INFO : Congratulations! Netlists match." -end - diff --git a/flow/platforms/sky130hd/lvs/sky130hd_lvs.py b/flow/platforms/sky130hd/lvs/sky130hd_lvs.py new file mode 100644 index 0000000000..297264d46e --- /dev/null +++ b/flow/platforms/sky130hd/lvs/sky130hd_lvs.py @@ -0,0 +1,436 @@ +"""SKY130 LVS deck, run by flow/util/klayout_lvs.py on the klayout module. + +Extraction for SKY130, translated line by line from KLayout's LVS +language. +""" + +from klayout_lvs import mos4 + +THREADS = 4 + +NMOS_CLASSES = ["nfet_01v8", "nfet_01v8_lvt", "nfet_g5v0d10v5", "nfet_01v8_nvt"] +PMOS_CLASSES = ["pfet_01v8", "pfet_01v8_hvt", "pfet_g5v0d10v5"] + + +def merge_parallel_series_stacks(circuit, device_class_names): + """Join the internal nets of parallel/series transistor stacks. + + Real GDS-extracted layout often ties 3+ transistor fingers to a single + shared internal diffusion node (interdigitated/folded layout), whereas + the idealized schematic CDL always has exactly 2 terminals per + internal fold node. This builds a signature for every qualifying + terminal on a net (any count), then requires the entire sorted + multiset of a net's terminal signatures to match another net's before + joining them, which refuses to conflate two nets that only partially + overlap in connectivity (two distinct internal series junctions in a + 3-stage stack). + + Signature keys are net names. Every unnamed net first gets a real, + persistent name (synth_): the binding hands back a new wrapper + object on each lookup, so neither object identity nor equality of the + wrappers identifies the same net reached two ways, and two distinct + anonymous nets must never share a key. + """ + synth_counter = 0 + for n in circuit.each_net(): + if not n.name: + synth_counter += 1 + n.name = "synth_%d" % synth_counter + + def net_key(net): + if net is None: + return "unconnected" + return net.name + + names = [n.lower() for n in device_class_names] + chains = [] + for net in circuit.each_net(): + if any(True for _ in net.each_pin()) or any( + True for _ in net.each_subcircuit_pin() + ): + continue + has_gate_terminal = False + for t in net.each_terminal(): + dc = t.device().device_class() + try: + if t.terminal_id() == dc.terminal_id("G"): + has_gate_terminal = True + except RuntimeError: + pass + if has_gate_terminal: + continue + term_sigs = [] + for t in net.each_terminal(): + d = t.device() + dc = d.device_class() + if dc.name.lower() not in names: + continue + tid_s = dc.terminal_id("S") + tid_g = dc.terminal_id("G") + tid_d = dc.terminal_id("D") + tid_b = dc.terminal_id("B") + role = t.terminal_id() + if role not in (tid_s, tid_d): + continue + opp = tid_d if role == tid_s else tid_s + term_sigs.append( + ( + net_key(d.net_for_terminal(opp)), + net_key(d.net_for_terminal(tid_g)), + net_key(d.net_for_terminal(tid_b)), + dc.name.lower(), + ) + ) + if len(term_sigs) < 2: + continue + chains.append((tuple(sorted(term_sigs)), net)) + + groups = {} + for sig, net in chains: + groups.setdefault(sig, []).append(net) + joined = set() + for nets_in_group in groups.values(): + uniq_nets = [] + for n in nets_in_group: + if n.name not in joined and n.name not in [u.name for u in uniq_nets]: + uniq_nets.append(n) + if len(uniq_nets) < 2: + continue + keep = uniq_nets[0] + joined.update(n.name for n in uniq_nets) + for other in uniq_nets[1:]: + circuit.join_nets(keep, other) + + +def converge_merge(circuit, nmos_classes, pmos_classes, max_iters=10): + """Merge parallel/series stacks until the device count stops + shrinking: a single pass can leave multi-finger devices partially + folded, e.g. sky130 cells where a "wide" transistor is laid out as + several minimum-width fingers in parallel.""" + prev_count = -1 + for _ in range(max_iters): + merge_parallel_series_stacks(circuit, nmos_classes) + merge_parallel_series_stacks(circuit, pmos_classes) + circuit.combine_devices() + count = sum(1 for _ in circuit.each_device()) + if count == prev_count: + return + prev_count = count + print( + "WARNING: converge_merge did not stabilize for %s after %d iterations" + % (circuit.name, max_iters) + ) + + +def find_net_by_name_component(circuit, component): + """The net whose comma-separated name has the component. + + KLayout's extraction can merge the ground net with tied-off signal + pins (constant-0 outputs) and name it "VSS,data_addr_o[0],...", which + an exact net_by_name("VSS") does not find. + """ + for n in circuit.each_net(): + if n.name and component in n.name.split(","): + return n + return None + + +def lvs(deck): + # layers definitions + ######################## + BOUND = deck.polygons(235, 4) + DNWELL = deck.polygons(64, 18) + PWRES = deck.polygons(64, 13) + NWELL = deck.polygons(64, 20) + NWELLTXT = deck.input(64, 5) + NWELLPIN = deck.polygons(64, 16) + SUBTXT = deck.input(122, 5) + SUBPIN = deck.input(64, 59) + DIFF = deck.polygons(65, 20) + TAP = deck.polygons(65, 44) + PSDM = deck.polygons(94, 20) + NSDM = deck.polygons(93, 44) + LVTN = deck.polygons(125, 44) + HVTR = deck.polygons(18, 20) + HVTP = deck.polygons(78, 44) + SONOS = deck.polygons(80, 20) + COREID = deck.polygons(81, 2) + STDCELL = deck.polygons(81, 4) + NPNID = deck.polygons(82, 20) + PNPID = deck.polygons(82, 44) + RPM = deck.polygons(86, 20) + URPM = deck.polygons(79, 20) + LDNTM = deck.polygons(11, 44) + HVNTM = deck.polygons(125, 20) + POLY = deck.polygons(66, 20) + POLYTXT = deck.input(66, 5) + POLYPIN = deck.polygons(66, 16) + HVI = deck.polygons(75, 20) + LICON = deck.polygons(66, 44) + NPC = deck.polygons(95, 20) + DIFFRES = deck.polygons(65, 13) + POLYRES = deck.polygons(66, 13) + POLYSHO = deck.polygons(66, 15) + DIODE = deck.polygons(81, 23) + LI = deck.polygons(67, 20) + LITXT = deck.input(67, 5) + LIPIN = deck.polygons(67, 16) + LIRES = deck.polygons(67, 13) + MCON = deck.polygons(67, 44) + MET1 = deck.polygons(68, 20) + MET1TXT = deck.input(68, 5) + MET1PIN = deck.polygons(68, 16) + MET1RES = deck.polygons(68, 13) + VIA1 = deck.polygons(68, 44) + MET2 = deck.polygons(69, 20) + MET2TXT = deck.input(69, 5) + MET2PIN = deck.polygons(69, 16) + MET2RES = deck.polygons(69, 13) + VIA2 = deck.polygons(69, 44) + MET3 = deck.polygons(70, 20) + MET3TXT = deck.input(70, 5) + MET3PIN = deck.polygons(70, 16) + MET3RES = deck.polygons(70, 13) + VIA3 = deck.polygons(70, 44) + MET4 = deck.polygons(71, 20) + MET4TXT = deck.input(71, 5) + MET4PIN = deck.polygons(71, 16) + MET4RES = deck.polygons(71, 13) + VIA4 = deck.polygons(71, 44) + MET5 = deck.polygons(72, 20) + MET5TXT = deck.input(72, 5) + MET5PIN = deck.polygons(72, 16) + MET5RES = deck.polygons(72, 13) + RDL = deck.polygons(74, 20) + RDLTXT = deck.input(74, 5) + RDLPIN = deck.polygons(74, 16) + GLASS = deck.polygons(76, 20) + CAPM = deck.polygons(89, 44) + CAPM2 = deck.polygons(97, 44) + LOWTAPD = deck.polygons(81, 14) + FILLOBSM1 = deck.polygons(62, 24) + FILLOBSM2 = deck.polygons(105, 52) + FILLOBSM3 = deck.polygons(107, 24) + FILLOBSM4 = deck.polygons(112, 4) + NCM = deck.polygons(92, 44) + + # Bulk layer for terminal provisioning + SUB = deck.polygons(236, 0) + # SUB = deck.polygon_layer() + + # Computed layers + PDIFF = DIFF & NWELL & PSDM + NTAP = TAP & NWELL & NSDM + PGATE = PDIFF & POLY + PSD = PDIFF - PGATE + STD_PGATE = PGATE - HVTP - NCM - HVI + HVT_PGATE = PGATE & HVTP - NCM - HVI + HV5_PGATE = PGATE - HVTP - NCM & HVI + + NDIFF = DIFF - NWELL & NSDM + PTAP = TAP - NWELL & PSDM + NGATE = NDIFF & POLY + NSD = NDIFF - NGATE + STD_NGATE = NGATE - NCM - LVTN - HVI + LVT_NGATE = NGATE - NCM & LVTN - HVI + HV5_NGATE = NGATE - NCM - LVTN & HVI + HV5NA_NGATE = NGATE - NCM & LVTN & HVI + + # drawing to physical + deck.device_scaling(1000000) + + # PMOS transistor device extraction + deck.extract_devices( + mos4("pfet_01v8"), + {"SD": PSD, "G": STD_PGATE, "tS": PSD, "tD": PSD, "tG": POLY, "W": NWELL}, + ) + deck.extract_devices( + mos4("pfet_01v8_hvt"), + {"SD": PSD, "G": HVT_PGATE, "tS": PSD, "tD": PSD, "tG": POLY, "W": NWELL}, + ) + deck.extract_devices( + mos4("pfet_g5v0d10v5"), + {"SD": PSD, "G": HV5_PGATE, "tS": PSD, "tD": PSD, "tG": POLY, "W": NWELL}, + ) + + # NMOS transistor device extraction + deck.extract_devices( + mos4("nfet_01v8"), + {"SD": NSD, "G": STD_NGATE, "tS": NSD, "tD": NSD, "tG": POLY, "W": SUB}, + ) + deck.extract_devices( + mos4("nfet_01v8_lvt"), + {"SD": NSD, "G": LVT_NGATE, "tS": NSD, "tD": NSD, "tG": POLY, "W": SUB}, + ) + deck.extract_devices( + mos4("nfet_g5v0d10v5"), + {"SD": NSD, "G": HV5_NGATE, "tS": NSD, "tD": NSD, "tG": POLY, "W": SUB}, + ) + deck.extract_devices( + mos4("nfet_01v8_nvt"), + {"SD": NSD, "G": HV5NA_NGATE, "tS": NSD, "tD": NSD, "tG": POLY, "W": SUB}, + ) + + # Define connectivity for netlist extraction + + # Inter-layer + deck.connect(SUB, PTAP) + deck.connect(NWELL, NTAP) + deck.connect(LICON, PTAP) + deck.connect(LICON, NTAP) + deck.connect(PSD, LICON) + deck.connect(NSD, LICON) + deck.connect(POLY, LICON) + deck.connect(LICON, LI) + deck.connect(LI, MCON) + deck.connect(MCON, MET1) + deck.connect(MET1, VIA1) + deck.connect(VIA1, MET2) + deck.connect(MET2, VIA2) + deck.connect(VIA2, MET3) + deck.connect(MET3, VIA3) + deck.connect(VIA3, MET4) + deck.connect(MET4, VIA4) + deck.connect(VIA4, MET5) + # Attaching labels + deck.connect(SUB, SUBTXT) + deck.connect(SUB, SUBPIN) + deck.connect(NWELL, NWELLTXT) + deck.connect(POLY, POLYTXT) + deck.connect(LI, LITXT) + deck.connect(MET1, MET1TXT) + deck.connect(MET2, MET2TXT) + deck.connect(MET3, MET3TXT) + deck.connect(MET4, MET4TXT) + deck.connect(MET5, MET5TXT) + + # Global + deck.connect_global(SUB, "VNB") + + # Merge 0-ohm tie resistors in the schematic into direct net shorts, + # so their structure matches the physical layout (which has no discrete + # device for these ties, just a metal/contact short). + schematic = deck.schematic + for c in schematic.each_circuit(): + if c.name != "SKY130_FD_SC_HD__CONB_1": + continue + to_remove = [] + for d in c.each_device(): + dc = d.device_class() + if dc.name == "RES" and d.parameter("R") == 0.0: + na = d.net_for_terminal(dc.terminal_id("A")) + nb = d.net_for_terminal(dc.terminal_id("B")) + if na and nb and na.name != nb.name: + c.join_nets(na, nb) + to_remove.append(d) + for d in to_remove: + c.remove_device(d) + + deck.same_circuits("sky130_fd_sc_hd__conb_1", "SKY130_FD_SC_HD__CONB_1") + + # Required to match transistors properly + for c in schematic.each_circuit(): + converge_merge(c, NMOS_CLASSES, PMOS_CLASSES) + + # Actually performs the extraction + netlist = deck.netlist() + + # Keep diode in both netlist and schematic, so it matches + c1 = netlist.circuit_by_name("sky130_fd_sc_hd__conb_1") + if c1: + c1.dont_purge = True + c2 = schematic.circuit_by_name("SKY130_FD_SC_HD__CONB_1") + if c2: + c2.dont_purge = True + + deck.split_gates("nfet_01v8") + deck.split_gates("nfet_01v8_lvt") + deck.split_gates("nfet_g5v0d10v5") + deck.split_gates("nfet_01v8_nvt") + deck.split_gates("pfet_01v8") + deck.split_gates("pfet_01v8_hvt") + deck.split_gates("pfet_g5v0d10v5") + + # The same multi-finger folding applies to the layout-extracted + # netlist. Empirically this must run after split_gates, not before: + # running it earlier (against the raw, still-undecomposed extracted + # device representation) produced more fragmented results, not fewer. + for c in netlist.each_circuit(): + converge_merge(c, NMOS_CLASSES, PMOS_CLASSES) + + # Ties VNB and VSS in the top cell. + for c in netlist.each_circuit(): + if c.name != deck.top_cell: + continue + n_vnb = find_net_by_name_component(c, "VNB") + n_vss = find_net_by_name_component(c, "VSS") + if n_vnb and n_vss and n_vnb.name != n_vss.name: + c.join_nets(n_vnb, n_vss) + + # Flatten cells which are present in one netlist only + deck.align() + + # SIMPLIFICATION of the netlist + netlist.simplify() + schematic.simplify() + + # Tolerances for the devices extracted parameters + for device in ("pfet_01v8", "pfet_01v8_hvt", "nfet_01v8", "nfet_01v8_lvt"): + for parameter in ("W", "L"): + deck.tolerance(device, parameter, absolute=0.001, relative=0.001) + + deck.equivalent_pins("*AND2_1", "A", "B") + deck.equivalent_pins("*AND2_2", "A", "B") + deck.equivalent_pins("*AND2_4", "A", "B") + deck.equivalent_pins("*AND3_1", "A", "B", "C") + deck.equivalent_pins("*AND3_2", "A", "B", "C") + deck.equivalent_pins("*AND3_4", "A", "B", "C") + deck.equivalent_pins("*AND4_1", "A", "B", "C", "D") + deck.equivalent_pins("*AND4_2", "A", "B", "C", "D") + deck.equivalent_pins("*AND4_4", "A", "B", "C", "D") + deck.equivalent_pins("*A21OI_1", "A1", "A2") + deck.equivalent_pins("*A21OI_2", "A1", "A2") + deck.equivalent_pins("*A21OI_4", "A1", "A2") + deck.equivalent_pins("*A211OI_1", "A1", "A2") + deck.equivalent_pins("*A211OI_1", "B1", "C1") + deck.equivalent_pins("*A211OI_2", "A1", "A2") + deck.equivalent_pins("*A211OI_2", "B1", "C1") + deck.equivalent_pins("*A211OI_4", "A1", "A2") + deck.equivalent_pins("*A211OI_4", "B1", "C1") + deck.equivalent_pins("*A221OI_1", "A1", "A2") + deck.equivalent_pins("*A221OI_1", "B1", "B2") + deck.equivalent_pins("*A22OI_1", "A1", "A2") + deck.equivalent_pins("*A22OI_1", "B1", "B2") + deck.equivalent_pins("*NAND2_1", "A", "B") + deck.equivalent_pins("*NAND2_2", "A", "B") + deck.equivalent_pins("*NAND2_4", "A", "B") + deck.equivalent_pins("*NAND3_1", "A", "B", "C") + deck.equivalent_pins("*NAND3_2", "A", "B", "C") + deck.equivalent_pins("*NAND3_4", "A", "B", "C") + deck.equivalent_pins("*NAND4_X1", "A", "B", "C", "D") + deck.equivalent_pins("*NAND4_2", "A", "B", "C", "D") + deck.equivalent_pins("*NAND4_4", "A", "B", "C", "D") + deck.equivalent_pins("*OR2_1", "A", "B") + deck.equivalent_pins("*OR2_2", "A", "B") + deck.equivalent_pins("*OR2_4", "A", "B") + deck.equivalent_pins("*OR3_1", "A", "B", "C") + deck.equivalent_pins("*OR3_2", "A", "B", "C") + deck.equivalent_pins("*OR3_4", "A", "B", "C") + deck.equivalent_pins("*OR4_1", "A", "B", "C", "D") + deck.equivalent_pins("*OR4_2", "A", "B", "C", "D") + deck.equivalent_pins("*OR4_4", "A", "B", "C", "D") + deck.equivalent_pins("*NOR2_1", "A", "B") + deck.equivalent_pins("*NOR2_2", "A", "B") + deck.equivalent_pins("*NOR2_4", "A", "B") + deck.equivalent_pins("*NOR3_1", "A", "B", "C") + deck.equivalent_pins("*NOR3_2", "A", "B", "C") + deck.equivalent_pins("*NOR3_4", "A", "B", "C") + deck.equivalent_pins("*NOR4_1", "A", "B", "C", "D") + deck.equivalent_pins("*NOR4_2", "A", "B", "C", "D") + deck.equivalent_pins("*NOR4_4", "A", "B", "C", "D") + deck.equivalent_pins("*XOR2_1", "A", "B") + deck.equivalent_pins("*XOR2_2", "A", "B") + deck.equivalent_pins("*XNOR2_1", "A", "B") + deck.equivalent_pins("*XNOR2_2", "A", "B") + + return deck.compare() diff --git a/flow/test/test_klayout_sh.py b/flow/test/test_klayout_sh.py index 40ace239c9..f7be6fbec9 100644 --- a/flow/test/test_klayout_sh.py +++ b/flow/test/test_klayout_sh.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -"""klayout.sh runs a KLayout executable: LVS, a DRC deck still in Ruby, -the gallery and the viewers. Without KLayout it stops with an error. With KLayout, the call +"""klayout.sh runs a KLayout executable: a deck still in Ruby, the +gallery and the viewers. Without KLayout it stops with an error. With KLayout, the call goes to it unchanged. """ @@ -40,16 +40,16 @@ def stream_args(self): "/flow/util/def2stream.py", ] - def test_lvs_still_needs_klayout(self): + def test_ruby_deck_still_needs_klayout(self): result = self.run_klayout( "", - "-b", + "-zz", "-rd", "in_gds=6_final.gds", "-rd", - "report_file=6_lvs.lvsdb", + "report_file=6_drc.lyrdb", "-r", - "/flow/platforms/sky130hd/lvs/sky130hd.lylvs", + "/flow/platforms/ihp-sg13g2/drc/sg13g2_minimal.lydrc", ) self.assertEqual(result.returncode, 1) self.assertIn("Error: KLayout not found", result.stdout) diff --git a/flow/util/klayout_lvs.py b/flow/util/klayout_lvs.py new file mode 100644 index 0000000000..839cb1697d --- /dev/null +++ b/flow/util/klayout_lvs.py @@ -0,0 +1,209 @@ +"""LVS decks in Python on the PyPI klayout module. + +KLayout's LVS language is its DRC language plus a netlist comparer +(src/lvs/lvs/built-in-macros/_lvs_netter.rb). This module adds to +klayout_drc.Deck what ORFS's LVS decks use: device extraction, the +schematic, the comparer's configuration and the comparison, with the +calls the LVS language makes, in its order: what is configured before +the netlist is extracted runs right after the extraction, what is +configured for the comparer runs when it is made. +""" + +import argparse +import os +import sys + +import klayout_drc +from klayout_drc import db + + +class LvsDeck(klayout_drc.Deck): + def __init__(self, in_gds, report_file, cdl_file, top_cell, threads=1): + super().__init__(in_gds, report_file, "LVS", mode="deep", threads=threads) + self.top_cell = top_cell + self.scaling = 1.0 + self.post_extract = [] + self.comparer_config = [] + self.info("Reading netlist: %s .." % cdl_file) + self.schematic = db.Netlist() + self.schematic.read(cdl_file, db.NetlistSpiceReader()) + + def _l2n_register(self, data): + if self.l2n is None: + self.l2n = db.LayoutVsSchematic(self.dss) + self.l2n.name = "LVS" + self.l2n.device_scaling = self.scaling + self.l2n_layers = {} + super()._l2n_register(data) + + def device_scaling(self, factor): + self.scaling = factor + if self.l2n is not None: + self.l2n.device_scaling = factor + + def extract_devices(self, extractor, layers): + """layers: terminal or layer name to Layer, as the LVS language's + hash; registered in the order of the sorted names, as it does.""" + selection = {} + for name in sorted(layers): + self._l2n_register(layers[name].data) + selection[name] = layers[name].data + self.l2n.extract_devices(extractor, selection) + return extractor.device_class() + + def connect_global(self, layer, name): + self._l2n_register(layer.data) + self.l2n.connect(layer.data) + self.l2n.connect_global(layer.data, name) + + def _l2n_data(self): + if not self.l2n.is_extracted(): + super()._l2n_data() + for f in self.post_extract: + f(self.l2n) + return self.l2n + + def netlist(self): + return self._l2n_data().netlist() + + def _extracted(self): + return self.l2n is not None and self.l2n.is_extracted() + + def _now_or_after_extraction(self, f): + if self._extracted(): + f(self.l2n) + else: + self.post_extract.append(f) + + def split_gates(self, device_name, circuit_pattern="*"): + def f(l2n): + dc = l2n.netlist().device_class_by_name(device_name) + if dc is None: + raise ValueError("'%s' is not a valid device name" % device_name) + for c in l2n.netlist().circuits_by_name(circuit_pattern): + dc.join_split_gates(c) + + self._now_or_after_extraction(f) + + def tolerance(self, device_class_name, parameter_name, absolute=0.0, relative=0.0): + def f(l2n): + dc = l2n.netlist().device_class_by_name(device_class_name) + if dc is not None and dc.has_parameter(parameter_name): + ep = db.EqualDeviceParameters( + dc.parameter_id(parameter_name), float(absolute), float(relative) + ) + if dc.equal_parameters is None: + dc.equal_parameters = ep + else: + dc.equal_parameters += ep + + self._now_or_after_extraction(f) + + def same_circuits(self, a, b): + def f(comparer): + circuit_a = a and self.netlist().circuit_by_name(a) + circuit_b = b and self.schematic.circuit_by_name(b) + if circuit_a and circuit_b: + comparer.same_circuits(circuit_a, circuit_b) + + self.comparer_config.append(f) + + def equivalent_pins(self, circuit, *pins): + def f(comparer): + circuit_b = self.schematic.circuit_by_name(circuit) + if circuit_b is None: + return + by_index = list(circuit_b.each_pin()) + ids = [] + for p in pins: + if isinstance(p, str): + pin = circuit_b.pin_by_name(p) + else: + pin = by_index[int(p)] + if pin is None: + raise ValueError( + "Not a valid pin in circuit '%s' in 'equivalent_pins': %s" + % (circuit, p) + ) + ids.append(pin.id()) + comparer.equivalent_pins(circuit_b, ids) + + self.comparer_config.append(f) + + def _comparer(self): + comparer = db.NetlistComparer() + for f in self.comparer_config: + f(comparer) + return comparer + + def align(self): + nl_a, nl_b = self.netlist(), self.schematic + comparer = self._comparer() + unmatched_a = comparer.unmatched_circuits_a(nl_a, nl_b) + top = self.l2n.internal_top_cell().name + if any(c.name == top for c in unmatched_a): + raise ValueError( + "Can't find a schematic counterpart for the top cell %s" % top + ) + for c in unmatched_a: + self.info("Flatten layout cell (no schematic): %s" % c.name) + nl_a.flatten_circuits(unmatched_a) + unmatched_b = comparer.unmatched_circuits_b(nl_a, nl_b) + for c in unmatched_b: + self.info("Flatten schematic circuit (no layout): %s" % c.name) + nl_b.flatten_circuits(unmatched_b) + + def compare(self): + self.netlist() + self.l2n.reference = self.schematic + return self.l2n.compare(self._comparer()) + + def write(self): + if self.l2n is not None and self.l2n.xref() is not None: + self.info("Writing LVS database: %s .." % self.report_file) + self.l2n.write(self.report_file, True) + + +def mos4(name): + return db.DeviceExtractorMOS4Transistor(name, False, None) + + +def parse_args(argv): + parser = argparse.ArgumentParser( + description="Run a Python LVS deck on a GDS/OAS file against a CDL" + " netlist and write a KLayout LVS database (.lvsdb)." + ) + parser.add_argument("--deck", required=True, help="the deck, a .py file") + parser.add_argument("--in-gds", required=True) + parser.add_argument("--cdl-file", required=True) + parser.add_argument("--top-cell", required=True) + parser.add_argument("--report-file", required=True) + return parser.parse_args(argv) + + +def main(argv): + args = parse_args(argv) + module = klayout_drc.load_deck(args.deck) + deck = LvsDeck( + args.in_gds, + args.report_file, + args.cdl_file, + args.top_cell, + threads=module.THREADS, + ) + match = module.lvs(deck) + deck.write() + if match: + print("INFO : Congratulations! Netlists match.") + else: + print("ERROR : Netlists don't match") + return 0 + + +if __name__ == "__main__": + # The deck imports these modules by name; run those, not __main__, + # so the objects it passes are the ones checked here. + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import klayout_lvs + + sys.exit(klayout_lvs.main(sys.argv[1:])) From 6d493cbbda2b7367b919ea5c7b14d94fe960a543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 08:48:43 +0100 Subject: [PATCH 06/11] ihp-sg13g2: DRC deck sg13g2_minimal in Python, density included MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, make drc on ihp-sg13g2 needed the KLayout application to run sg13g2_minimal.lydrc. Now it runs sg13g2_minimal_drc.py on the klayout Python module. The Ruby deck adds ext_* methods to the DRC language's layer; the Python deck adds the same functions to klayout_drc.Layer, line by line: the space, width and separation checks with their touch-point and intersecting-edge handling (EdgePairOperator subclasses as in Ruby), the rectangle and square filters as universal DRC expressions (CompoundRegionOperationNode), and the global and 800 x 800 um density windows. klayout_drc.py gets what these need from the DRC language: with_density/without_density on a TilingProcessor with the same expression script, tile options, padding_ignore, labels and texts, raw, merge, dup, select_inside, with_distance, sizing modes and the ortho edge filter. What only the application had is gone: the file dialog and RBA::CellView::active when no GDS is given (make drc always gives one), and the -rd variables log_file, cell, threads and densityRules, which the Makefile never set; density rules stay on and the deck runs single-threaded, as before. Checked against the Ruby deck run by the KLayout 0.30.0 application on the same ihp-sg13g2/gcd GDS, Python on klayout 0.30.13: clean GDS: 8 violations in 8 categories (global and windowed density), both, same markers seeded GDS: 24 violations in 20 categories, both, same markers (narrow and close Metal1, narrow Metal2, GatPoly, Activ and TopMetal1, Via1 of the wrong size, Cont spacing with a corner touch, GatPoly spacing, a Metal1 pin outside Metal1, and density) runtime: 4.3 s Ruby, 2.6 s Python sg13g2_maximal.lydrc, which no flow uses, and the commented-out LVS deck stay as they are. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- flow/platforms/ihp-sg13g2/config.mk | 2 +- .../ihp-sg13g2/drc/sg13g2_minimal.lydrc | 1291 ----------------- .../ihp-sg13g2/drc/sg13g2_minimal_drc.py | 1138 +++++++++++++++ flow/test/test_klayout_sh.py | 2 +- flow/util/klayout_drc.py | 199 ++- 5 files changed, 1330 insertions(+), 1302 deletions(-) delete mode 100644 flow/platforms/ihp-sg13g2/drc/sg13g2_minimal.lydrc create mode 100644 flow/platforms/ihp-sg13g2/drc/sg13g2_minimal_drc.py diff --git a/flow/platforms/ihp-sg13g2/config.mk b/flow/platforms/ihp-sg13g2/config.mk index 577d5c41ea..e6b64e26a8 100644 --- a/flow/platforms/ihp-sg13g2/config.mk +++ b/flow/platforms/ihp-sg13g2/config.mk @@ -162,7 +162,7 @@ export GND_NETS_VOLTAGES ?= VSS 0.0 export IR_DROP_LAYER ?= Metal1 # DRC Check -export KLAYOUT_DRC_FILE ?= $(PLATFORM_DIR)/drc/sg13g2_minimal.lydrc +export KLAYOUT_DRC_FILE ?= $(PLATFORM_DIR)/drc/sg13g2_minimal_drc.py #LVS Check export CDL_FILE ?= $(PLATFORM_DIR)/cdl/sg13g2_stdcell.cdl diff --git a/flow/platforms/ihp-sg13g2/drc/sg13g2_minimal.lydrc b/flow/platforms/ihp-sg13g2/drc/sg13g2_minimal.lydrc deleted file mode 100644 index 767ac88e84..0000000000 --- a/flow/platforms/ihp-sg13g2/drc/sg13g2_minimal.lydrc +++ /dev/null @@ -1,1291 +0,0 @@ - - - - - - drc - - - - false - false - - true - drc_scripts - tools_menu.drc.end - dsl - drc-dsl-xml - # Supported variables that can be set using "-rd <name>=<value>" on the command line: -# log_file - path to the log file [default: no log file] -# in_gds - path to the GDS layout to check (required in batch mode) -# cell - name of the cell to check -# report_file - path to the report database [default: sg13g2_minimal.lyrdb in the layout directory] - -# to set logfile: -rd logfile="sg13g2_minimal.log" -if $log_file - log_file($log_file) -end - -application = RBA::Application.instance -main_window = application.main_window -if main_window and not $in_gds - curr_layout_view = main_window.current_view() - unless curr_layout_view - layout_path = RBA::FileDialog::ask_open_file_name("Chose your layout file.", ".", "GDSII files (*.GDS *.gds *.GDS.gz *.gds.gz *.GDS2 *.gds2 *.GDS2.gz *.gds2.gz);; All files (*)") - unless layout_path - return - end - main_window.load_layout(layout_path, 1) - curr_layout_view = main_window.current_view() - end - active_filename = RBA::CellView::active.filename - if active_filename != "" - report_dir = File.dirname(active_filename) - else - report_dir = "/tmp" - end - active_layout = RBA::CellView::active.layout - active_cellname = RBA::CellView::active.cell_name - source(active_layout, active_cellname) -else - log("DRC: batch mode") - # to set input layout: -rd in_gds="path to GDS file" - # to set cell: -rd cell="topcell" - if $cell - active_cellname = $cell - log("Active cell: " + active_cellname) - source($in_gds, active_cellname) - active_layout = source.layout - else - source($in_gds) - active_layout = source.layout - active_cellname = source.cell_name - end - report_dir = File.dirname($in_gds) -end - -unless active_layout or $in_gds - raise("layout file must be defined on command line or via 'in_gds' script variable") -end - -if active_layout.dbu != 0.001 - puts "WARNING: Layout dbu value (" + active_layout.dbu.to_s + " ) deviates from rule file dbu value (0.001). This will scale the layout and may not be intended." -end - -report_dir = File.expand_path(report_dir) -report_file = report_dir + "/sg13g2_minimal.lyrdb" -# to set report file: -rd report_file="sg13g2_minimal.lyrdb" -if $report_file - report_file = File.expand_path($report_file) -end - -report("design rules: sg13g2_minimal | layout cell: " + active_cellname, report_file) - -deep - -if $threads - threads($threads.to_i) -end - -$drc_error_count = 0 - -class DRC::DRCLayer - unless method_defined?(:original_output) - alias_method :original_output, :output - end - - def output(*args) - count = self.hier_count() - $drc_error_count += count - puts("%s: %d" % [args[0], count]) - original_output(*args) - end -end - -# Initial definitions of control flow variables -# Strings from the command line have to be converted -if defined? $densityRules - $densityRules = $densityRules.to_s.downcase == "true" -else - $densityRules = true -end - -class AbuttingEdges < RBA::EdgePairToEdgeOperator - def initialize - self.is_isotropic_and_scale_invariant - end - - def process(edge_pair) - if edge_pair.first.coincident?(edge_pair.second) - if edge_pair.first.length < edge_pair.second.length - return [ edge_pair.first ] - else - return [ edge_pair.second ] - end - else - return [] - end - end -end - -class IntersectingEdgeErrorFilter < RBA::EdgePairOperator - def initialize(abutting_edges) - @abutting_edges = abutting_edges - end - - def process(edge_pair) - ip = nil - if edge_pair.first.p1 == edge_pair.second.p1 or - edge_pair.first.p1 == edge_pair.second.p2 - ip = edge_pair.first.p1 - elsif edge_pair.first.p2 == edge_pair.second.p1 or - edge_pair.first.p2 == edge_pair.second.p2 - ip = edge_pair.first.p2 - end - if ip and not edge_pair.first.coincident?(edge_pair.second) - edges = RBA::Edges::new([ edge_pair.first, edge_pair.second ]) - region = @abutting_edges.extents(1) - interacting_edges = edges.interacting(region) - if interacting_edges.count == 2 - ep = edge_pair.normalized - v1 = ep.first.swapped_points.d - v2 = ep.second.d - angle = Math.acos((v1*v2)/(v1.length*v2.length))/Math::PI*180 - if angle >= 90 - return [] - end - end - end - return [ edge_pair ] - end -end - -class IntersectingEdgeErrorAngleFilter < RBA::EdgePairOperator - def initialize(min_angle, max_angle, include_min_angle, include_max_angle) - self.is_isotropic_and_scale_invariant - @min_angle = min_angle - @max_angle = max_angle - if include_min_angle - @min_angle -= 1e-6 - else - @min_angle += 1e-6 - end - if include_max_angle - @max_angle += 1e-6 - else - @max_angle -= 1e-6 - end - end - - def process(edge_pair) - ip = nil - if edge_pair.first.p1 == edge_pair.second.p1 or - edge_pair.first.p1 == edge_pair.second.p2 - ip = edge_pair.first.p1 - elsif edge_pair.first.p2 == edge_pair.second.p1 or - edge_pair.first.p2 == edge_pair.second.p2 - ip = edge_pair.first.p2 - end - if ip - ep = edge_pair.normalized - v1 = ep.first.swapped_points.d - v2 = ep.second.d - angle = Math.acos((v1*v2)/(v1.length*v2.length))/Math::PI*180 - if (angle < @min_angle) or (angle > @max_angle) - return [] - end - end - return [ edge_pair ] - end -end - -class DRC::DRCEngine - def find_intersecting_edges_errors(dbu_value, - error_edge_pairs_90, - error_edge_pairs_180, - error_edge_pairs_90_180 = nil, - inverse_error_edge_pairs_90 = nil, - inverse_error_edge_pairs_180 = nil, - options = {}) - consider_intersecting_edges = options.fetch(:consider_intersecting_edges, false) - consider_touch_points = options.fetch(:consider_touch_points, false) - ignore_non_axis_aligned_edges = options.fetch(:ignore_non_axis_aligned_edges, false) - min_angle = options.fetch(:min_angle, 0) - max_angle = options.fetch(:max_angle, 90) - include_min_angle = options.fetch(:include_min_angle, true) - include_max_angle = options.fetch(:include_max_angle, false) - area_of_right_angle = dbu_value**2/2 - errors_ep = RBA::EdgePairs::new() - touch_point_errors_ep = RBA::EdgePairs::new() - abutting_edges_errors_ep = RBA::EdgePairs::new() - intersecting_edges_error_candidates = Hash.new() - no_touch_point_error = Hash.new() - error_edge_pairs_90.data.each do |edge_pair| - ip = nil - if edge_pair.first.p1 == edge_pair.second.p1 or - edge_pair.first.p1 == edge_pair.second.p2 - ip = edge_pair.first.p1 - elsif edge_pair.first.p2 == edge_pair.second.p1 or - edge_pair.first.p2 == edge_pair.second.p2 - ip = edge_pair.first.p2 - end - if ip and !edge_pair.first.is_degenerate? and !edge_pair.second.is_degenerate? - if (edge_pair.first.contains?(edge_pair.second.p1) and - edge_pair.first.contains?(edge_pair.second.p2)) or - (edge_pair.second.contains?(edge_pair.first.p1) and - edge_pair.second.contains?(edge_pair.first.p2)) - no_touch_point_error[ip] = true - end - end - end - if consider_intersecting_edges or consider_touch_points - touch_point_candidates = Hash.new() - touch_point_errors = Hash.new() - if inverse_error_edge_pairs_90 and inverse_error_edge_pairs_180 - (inverse_error_edge_pairs_90 + inverse_error_edge_pairs_180).data.each do |edge_pair| - ip = edge_pair.first.intersection_point(edge_pair.second) - if ip - if touch_point_candidates[ip] - touch_point_errors[ip] = true - touch_point_candidates.delete(ip) - elsif !no_touch_point_error[ip] - touch_point_candidates[ip] = edge_pair - end - end - end - end - touch_point_candidates = Hash.new() - if !error_edge_pairs_90_180 - error_edge_pairs_90_180 = error_edge_pairs_90 + error_edge_pairs_180 - end - error_edge_pairs_90_180.data.each do |edge_pair| - ip = nil - if edge_pair.first.p1 == edge_pair.second.p1 or - edge_pair.first.p1 == edge_pair.second.p2 - ip = edge_pair.first.p1 - elsif edge_pair.first.p2 == edge_pair.second.p1 or - edge_pair.first.p2 == edge_pair.second.p2 - ip = edge_pair.first.p2 - elsif edge_pair.first.intersection_point(edge_pair.second) - abutting_edges_errors_ep.insert(edge_pair) - end - if ip and !edge_pair.first.is_degenerate? and !edge_pair.second.is_degenerate? - intersecting_edges_error_candidates[ip] = edge_pair - if touch_point_errors[ip] - touch_point_errors_ep.insert(edge_pair) - intersecting_edges_error_candidates.delete(ip) - elsif touch_point_candidates[ip] - touch_point_errors_ep.insert(edge_pair) - touch_point_errors_ep.insert(touch_point_candidates[ip]) - touch_point_candidates.delete(ip) - intersecting_edges_error_candidates.delete(ip) - elsif !no_touch_point_error[ip] - touch_point_candidates[ip] = edge_pair - end - end - end - if consider_intersecting_edges - errors_ep = RBA::EdgePairs::new(intersecting_edges_error_candidates.values) - errors_ep.process(IntersectingEdgeErrorAngleFilter::new(min_angle, max_angle, include_min_angle, include_max_angle)) - if min_angle == 0 and include_min_angle - errors_ep = errors_ep + abutting_edges_errors_ep - end - end - end - if ignore_non_axis_aligned_edges - errors_ep = errors_ep.with_angle_both(RBA::Edges::OrthoEdges, false) - end - if consider_touch_points - errors_ep = errors_ep + touch_point_errors_ep - end - return DRC::DRCLayer::new(self, errors_ep) - end -end - -class DRC::DRCLayer - def ext_merged(min_coherence, overlap_count) - args = [min_coherence, overlap_count] - return DRCLayer::new(@engine, @engine._tcmd(self.data, 0, self.data.class, :merged, *args)) - end - - def ext_merge(min_coherence, overlap_count) - args = [min_coherence, overlap_count] - if @engine.is_tiled? - self.data = @engine._tcmd(self.data, 0, self.data.class, :merged, *args) - else - @engine._tcmd(self.data, 0, self.data.class, :merge, *args) - end - return self - end - - def ext_and(other) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - output_layer = self & other - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_with_area(constraint) - lower_bound = nil - upper_bound = nil - output_layer = nil - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - constraint.each do |expression| - relation = expression[0] - value = expression[1] - if relation == ">" - lower_bound = value + 1.dbu**2 - elsif relation == "<" - upper_bound = value - elsif relation == "==" - output_layer = self.with_area(value) - elsif relation == "!=" - output_layer = self.without_area(value) - elsif relation == ">=" - lower_bound = value - elsif relation == "<=" - upper_bound = value + 1.dbu**2 - else - raise "invalid expression" - end - end - if lower_bound or upper_bound - output_layer = self.with_area(lower_bound, upper_bound) - end - self.data.min_coherence = self_min_coherence_state - return output_layer - end - - def ext_constraint_satisfied(value, constraint) - output_bool = true - constraint.each do |expression| - if expression[0] == ">" - output_bool = output_bool && (value > expression[1]) - elsif expression[0] == "<" - output_bool = output_bool && (value < expression[1]) - elsif expression[0] == "==" - output_bool = output_bool && (value == expression[1]) - elsif expression[0] == "!=" - output_bool = output_bool && (value != expression[1]) - elsif expression[0] == ">=" - output_bool = output_bool && (value >= expression[1]) - elsif expression[0] == "<=" - output_bool = output_bool && (value <= expression[1]) - else - raise "invalid expression" - end - end - return output_bool - end - - def ext_covering(other) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - output_layer = self.covering(other.inside(self)) - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_separation_at_intersecting_edges(other, - value, - metric=RBA::Region::Euclidian, - consider_intersecting_edges=false, - consider_touch_points=false, - ignore_non_axis_aligned_edges=false, - min_angle=0, - max_angle=90, - include_min_angle=true, - include_max_angle=false, - polygons=false) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - if metric.is_a?(DRC::DRCMetrics) - metric = metric.value - end - if value.is_a? Float - dbu_value = (value/1.dbu).round - else - dbu_value = value - end - error_edge_pairs_90 = DRC::DRCLayer::new(@engine, - self.data.separation_check(other.data, dbu_value, false, metric, 90, 1, nil)) - error_edge_pairs_90_180 = DRC::DRCLayer::new(@engine, - self.data.separation_check(other.data, dbu_value, false, metric, 180)) - width_error_edge_pairs_90 = DRC::DRCLayer::new(@engine, - self.data.width_check(dbu_value, false, metric, 90, 1, nil) + - other.data.width_check(dbu_value, false, metric, 90, 1, nil)) - width_error_edge_pairs_180 = DRC::DRCLayer::new(@engine, - self.data.width_check(dbu_value, false, metric, 180, nil, 1) + - other.data.width_check(dbu_value, false, metric, 180, nil, 1)) - separation_errors = @engine.find_intersecting_edges_errors( - dbu_value, - error_edge_pairs_90, - nil, - error_edge_pairs_90_180, - width_error_edge_pairs_90, - width_error_edge_pairs_180, - { - consider_intersecting_edges: consider_intersecting_edges, - consider_touch_points: consider_touch_points, - ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges, - min_angle: min_angle, - max_angle: max_angle, - include_min_angle: include_min_angle, - include_max_angle: include_max_angle - } - ) - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - if polygons - separation_errors = separation_errors.polygons.ext_merge(true, 0) - end - return separation_errors - end - - def ext_separation(other, - value, - metric: @engine.euclidian, - consider_intersecting_edges: true, - consider_touch_points: true, - consider_overlaps: false, - ignore_non_axis_aligned_edges: false, - inside_edges_are_errors: false, - min_angle: 0, - max_angle: 90, - include_min_angle: true, - include_max_angle: false, - polygon_output: false) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - if consider_overlaps - output_layer = self.separation(other, value, metric, - @engine.angle_limit(180), @engine.without_touching_corners) - abutting_edges = output_layer.data.processed(AbuttingEdges::new) - output_layer.data.process(IntersectingEdgeErrorFilter::new(abutting_edges)) - else - angle_limit = max_angle - if include_max_angle - angle_limit += 1e-6 - end - output_layer = self.separation(other, value, metric, @engine.angle_limit(angle_limit)) - if !consider_intersecting_edges and !consider_touch_points - output_layer = output_layer.without_distance(0) - if ignore_non_axis_aligned_edges - output_layer = output_layer.with_angle(@engine.ortho, @engine.both) - end - elsif (consider_intersecting_edges ^ consider_touch_points) or - !(min_angle==0 and max_angle==90 and include_min_angle and !include_max_angle) - intersecting_edges_errors = output_layer.with_distance(0).edges - candidate_layer1 = self.interacting(intersecting_edges_errors) - candidate_layer2 = other.interacting(intersecting_edges_errors) - output_layer = output_layer.without_distance(0) - output_layer = output_layer + candidate_layer1.ext_separation_at_intersecting_edges( - candidate_layer2, - value, - metric, - consider_intersecting_edges, - consider_touch_points, - ignore_non_axis_aligned_edges, - min_angle, - max_angle, - include_min_angle, - include_max_angle, - false) - end - end - if inside_edges_are_errors - output_layer = output_layer.join(self.and(other).edges.ext_convert_to_edge_pair_layer()) - end - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - if polygon_output - output_layer = output_layer.polygons.ext_merge(true, 0) - end - return output_layer - end - - def ext_not(other) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - output_layer = self - other - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_or(other, *further_layers) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - output_layer = self.join(other) - further_layers.each do |further_layer| - further_layer_min_coherence_state = further_layer.data.min_coherence? - further_layer.data.min_coherence = true - output_layer = output_layer.join(further_layer) - further_layer.data.min_coherence = further_layer_min_coherence_state - end - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_rectangles(axis_aligned = false, use_bbox = false, constraint1 = nil, constraint2 = nil, aspect_ratio_constraint = nil, inverted: false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - if ( ( constraint1 && ( !constraint2 || constraint1.length() > 1 || constraint1[0][0] != "==") ) || - ( constraint2 && ( constraint2.length() > 1 || constraint2[0][0] != "==" ) ) || - ( constraint1 && constraint2 && constraint1[0][1] != constraint2[0][1] ) ) - raise "ext_rectangle: unsupported options" - end - square = constraint1 ? true : false - shape_filter = - if use_bbox - @engine.extents - elsif axis_aligned - @engine.rectangles - else - @engine.if_all((@engine.corners == 270).count == 4, @engine.corners.count == 4) - end - if square - if use_bbox - shape_filter = @engine.if_all((@engine.extents.length == constraint1[0][1]).count == 4) - else - square_filter = (@engine.length == constraint1[0][1]).count == 4 - shape_filter = @engine.if_all(shape_filter, square_filter) - end - end - if inverted - output_layer = self.drc(! shape_filter) - else - output_layer = self.drc(shape_filter) - end - self.data.min_coherence = self_min_coherence_state - return output_layer - end - - def ext_space_at_intersecting_edges(value, - metric=RBA::Region::Euclidian, - consider_intersecting_edges=false, - consider_touch_points=false, - ignore_non_axis_aligned_edges=false, - min_angle=0, - max_angle=90, - include_min_angle=true, - include_max_angle=false, - polygons=false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - if metric.is_a?(DRC::DRCMetrics) - metric = metric.value - end - if value.is_a? Float - dbu_value = (value/1.dbu).round - else - dbu_value = value - end - error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.space_check(dbu_value, false, metric, 90, 1, nil)) - error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.space_check(dbu_value, false, metric, 180, nil, 1)) - width_error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.width_check(dbu_value, false, metric, 90, 1, nil)) - width_error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.width_check(dbu_value, false, metric, 180, nil, 1)) - space_errors = @engine.find_intersecting_edges_errors( - dbu_value, - error_edge_pairs_90, - error_edge_pairs_180, - nil, - width_error_edge_pairs_90, - width_error_edge_pairs_180, - { - consider_intersecting_edges: consider_intersecting_edges, - consider_touch_points: consider_touch_points, - ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges, - min_angle: min_angle, - max_angle: max_angle, - include_min_angle: include_min_angle, - include_max_angle: include_max_angle - } - ) - self.data.min_coherence = self_min_coherence_state - if polygons - space_errors = space_errors.polygons.ext_merge(true, 0) - end - return space_errors - end - - def ext_space(value, - metric: @engine.euclidian, - consider_intersecting_edges: true, - consider_touch_points: true, - ignore_non_axis_aligned_edges: false, - min_angle: 0, - max_angle: 90, - include_min_angle: true, - include_max_angle: false, - polygon_output: false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - output_layer = self.space(value, metric, @engine.angle_limit(max_angle)) - if !consider_intersecting_edges and !consider_touch_points - output_layer = output_layer.with_distance(1, nil) - if ignore_non_axis_aligned_edges - output_layer = output_layer.with_angle(@engine.ortho, @engine.both) - end - elsif consider_intersecting_edges ^ consider_touch_points - candidate_layer = self.interacting(output_layer.with_distance(0).edges) - output_layer = output_layer.with_distance(1, nil) - output_layer = output_layer + candidate_layer.ext_space_at_intersecting_edges( - value, - metric, - consider_intersecting_edges, - consider_touch_points, - ignore_non_axis_aligned_edges, - min_angle, - max_angle, - include_min_angle, - include_max_angle, - false) - end - self.data.min_coherence = self_min_coherence_state - if polygon_output - output_layer = output_layer.polygons.ext_merge(true, 0) - end - return output_layer - end - - def ext_width_at_intersecting_edges(value, - metric=RBA::Region::Euclidian, - consider_intersecting_edges=false, - consider_touch_points=false, - ignore_non_axis_aligned_edges=false, - min_angle=0, - max_angle=90, - include_min_angle=true, - include_max_angle=false, - polygons=false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - if metric.is_a?(DRC::DRCMetrics) - metric = metric.value - end - if value.is_a? Float - dbu_value = (value/1.dbu).round - else - dbu_value = value - end - error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.width_check(dbu_value, false, metric, 90, 1, nil)) - error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.width_check(dbu_value, false, metric, 180, nil, 1)) - space_error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.space_check(dbu_value, false, metric, 90, 1, nil)) - space_error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.space_check(dbu_value, false, metric, 180, nil, 1)) - width_errors = @engine.find_intersecting_edges_errors( - dbu_value, - error_edge_pairs_90, - error_edge_pairs_180, - nil, - space_error_edge_pairs_90, - space_error_edge_pairs_180, - { - consider_intersecting_edges: consider_intersecting_edges, - consider_touch_points: consider_touch_points, - ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges, - min_angle: min_angle, - max_angle: max_angle, - include_min_angle: include_min_angle, - include_max_angle: include_max_angle - } - ) - self.data.min_coherence = self_min_coherence_state - if polygons - width_errors = width_errors.polygons.ext_merge(true, 0) - end - return width_errors - end - - def ext_width(value, - metric: @engine.euclidian, - consider_intersecting_edges: true, - consider_touch_points: true, - ignore_non_axis_aligned_edges: false, - min_angle: 0, - max_angle: 90, - include_min_angle: true, - include_max_angle: false, - polygon_output: false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = false - output_layer = self.width(value, metric, @engine.angle_limit(max_angle)) - if !consider_intersecting_edges and !consider_touch_points - output_layer = output_layer.with_distance(1, nil) - if ignore_non_axis_aligned_edges - output_layer = output_layer.with_angle(@engine.ortho, @engine.both) - end - elsif consider_intersecting_edges ^ consider_touch_points - candidate_layer = self.interacting(output_layer.with_distance(0).edges) - output_layer = output_layer.with_distance(1, nil) - output_layer = output_layer + candidate_layer.ext_width_at_intersecting_edges( - value, - metric, - consider_intersecting_edges, - consider_touch_points, - ignore_non_axis_aligned_edges, - min_angle, - max_angle, - include_min_angle, - include_max_angle, - false) - end - if polygon_output - output_layer = output_layer.polygons.ext_merge(true, 0) - end - self.data.min_coherence = self_min_coherence_state - return output_layer - end - - def ext_interacting_with_text(text_layer, text) - if text_layer.is_a? Integer - text_layer = @engine.labels(text_layer) - end - initial_merged_semantics = self.data.merged_semantics? - self.data.merged_semantics = false - result = self.interacting(text_layer.texts(text)) - self.data.merged_semantics = initial_merged_semantics - return result - end - - def _ext_with_density(inverse, - range, - *args, - tiles_stay_inside: false) - if inverse - method = :without_density - else - method = :with_density - end - if self.is_merged? - merged_layer = self - else - merged_layer = self.ext_merged(true, 0) - end - origin = 'cc' - tile_size = nil - tile_step = nil - arguments = [range] - args.each do |a| - if a.is_a?(DRC::DRCTileSize) - tile_size = a - arguments.push(tile_size) - elsif a.is_a?(DRC::DRCTileStep) - tile_step = a - arguments.push(tile_step) - elsif a.is_a?(String) - origin = a - else - raise "argument error" - end - end - readonly_extent = DRC::DRCLayer::new(@engine, RBA::Region::new(@engine.source.cell_obj.bbox)) - bbox = readonly_extent.bbox - if origin == 'll' - origin_x = bbox.left - origin_y = bbox.bottom - if tile_size and tile_step and (tile_size.get[0] != tile_step.get[0] or tile_size.get[1] != tile_step.get[1]) - origin_x = bbox.left + (tile_size.get[0]-tile_step.get[0])/2 - origin_y = bbox.bottom + (tile_size.get[1]-tile_step.get[1])/2 - end - tile_origin = DRC::DRCTileOrigin::new(origin_x, origin_y) - arguments.push(tile_origin) - elsif origin == 'cc' - origin_x = bbox.center.x - origin_y = bbox.center.y - tile_origin = DRC::DRCTileOrigin::new(origin_x, origin_y) - else - raise "Unknown origin: 'cc' or 'll' expected" - end - if tile_size - boundary_layer = DRC::DRCLayer::new(@engine, RBA::Region::new(bbox.to_itype(@engine.dbu))) - tile_boundary = DRC::DRCTileBoundary::new(boundary_layer) - if tiles_stay_inside - if origin != 'll' - raise "Option 'tiles_stay_inside' requires origin='ll'" - end - tile_width = tile_size.get[0] - tile_height = tile_size.get[1] - enlarged_bbox = bbox.dup - if tile_width > bbox.width - enlarged_bbox.right = bbox.left + tile_width - end - if tile_height > bbox.height - enlarged_bbox.top = bbox.bottom + tile_height - end - enlarged_boundary_layer = DRC::DRCLayer::new(@engine, RBA::Region::new(enlarged_bbox.to_itype(@engine.dbu))) - if tile_step - tile_step_x = tile_step.get[0] - tile_step_y = tile_step.get[1] - else - tile_step_x = tile_width - tile_step_y = tile_height - end - tile_count_x = 1 + ((bbox.width - tile_width) / tile_step_x).floor() - tile_count_x = tile_count_x < 1 ? 1 : tile_count_x - tile_count_y = 1 + ((bbox.height - tile_height) / tile_step_y).floor() - tile_count_y = tile_count_y < 1 ? 1 : tile_count_y - if tile_count_x == 1 and tile_count_y == 1 - tile_count = DRC::DRCTileCount::new(tile_count_x, 2) - else - tile_count = DRC::DRCTileCount::new(tile_count_x, tile_count_y) - end - result = merged_layer.public_send(method, *arguments, tile_count, tile_boundary, @engine.padding_ignore) - need_top_row = false - need_right_column = false - if bbox.height > tile_height + (tile_count_y-1)*tile_step_y - need_top_row = true - tile_origin2 = DRC::DRCTileOrigin::new(tile_origin.get[0], tile_origin.get[1]+bbox.height-tile_height) - tile_count = DRC::DRCTileCount::new([2, tile_count_x].max, 1) - result += merged_layer.public_send(method, *arguments, tile_origin2, tile_count, tile_boundary, @engine.padding_ignore) - end - if bbox.width > tile_width + (tile_count_x-1)*tile_step_x - need_right_column = true - tile_origin2 = DRC::DRCTileOrigin::new(tile_origin.get[0]+bbox.width-tile_width, tile_origin.get[1]) - tile_count = DRC::DRCTileCount::new(1, [2, tile_count_y].max) - result += merged_layer.public_send(method, *arguments, tile_origin2, tile_count, tile_boundary, @engine.padding_ignore) - end - if (need_top_row and bbox.width > tile_width) or (need_right_column and bbox.height > tile_height) - tile_origin2 = DRC::DRCTileOrigin::new(tile_origin.get[0]+bbox.width-tile_width, tile_origin.get[1]+bbox.height-tile_height) - tile_count = DRC::DRCTileCount::new(1, 2) - result += merged_layer.public_send(method, *arguments, tile_origin2, tile_count, tile_boundary, @engine.padding_ignore) - end - result.raw.select_inside(enlarged_boundary_layer) - else - result = merged_layer.public_send(method, *arguments, tile_boundary, @engine.padding_ignore) - end - return result.and(boundary_layer) - else - tile_size = DRC::DRCTileSize::new(bbox.width, bbox.height) - tile_count = DRC::DRCTileCount::new(1,3) - enlarged_bbox = bbox.enlarged(1.1).to_itype(@engine.dbu) - boundary_layer = DRC::DRCLayer::new(@engine, RBA::Region::new(enlarged_bbox)) - tile_boundary = DRC::DRCTileBoundary::new(boundary_layer) - result = merged_layer.public_send(method, *arguments, tile_size, tile_count, tile_boundary, @engine.padding_ignore) - return result.raw.overlapping(DRC::DRCLayer::new(@engine, RBA::Region::new(bbox.to_itype(@engine.dbu)))) - end - end - - def ext_with_density(*args, **kargs) - self._ext_with_density(false, *args, **kargs) - end - - def ext_without_density(*args, **kargs) - self._ext_with_density(true, *args, **kargs) - end - - def ext_outside(other) - output_layer = self.dup.raw.outside(other) - output_layer.data.merged_semantics = true - return output_layer - end -end - -$start_time = Time.now - -Activ = source.polygons("1/0") -Activ_pin = source.polygons("1/2") -Activ_filler = source.polygons("1/22") -BiWind = source.polygons("3/0") -GatPoly = source.polygons("5/0") -GatPoly_pin = source.polygons("5/2") -GatPoly_filler = source.polygons("5/22") -Cont = source.polygons("6/0") -Metal1 = source.polygons("8/0") -Metal1_pin = source.polygons("8/2") -Metal1_filler = source.polygons("8/22") -Metal1_slit = source.polygons("8/24") -Passiv = source.polygons("9/0") -Metal2 = source.polygons("10/0") -Metal2_pin = source.polygons("10/2") -Metal2_filler = source.polygons("10/22") -Metal2_slit = source.polygons("10/24") -BasPoly = source.polygons("13/0") -Via1 = source.polygons("19/0") -SRAM = source.polygons("25/0") -TRANS = source.polygons("26/0") -Via2 = source.polygons("29/0") -Metal3 = source.polygons("30/0") -Metal3_pin = source.polygons("30/2") -Metal3_filler = source.polygons("30/22") -Metal3_slit = source.polygons("30/24") -DeepCo = source.polygons("35/0") -EdgeSeal = source.polygons("39/0") -ThickGateOx = source.polygons("44/0") -Via3 = source.polygons("49/0") -Metal4 = source.polygons("50/0") -Metal4_pin = source.polygons("50/2") -Metal4_filler = source.polygons("50/22") -Metal4_slit = source.polygons("50/24") -EmPoly = source.polygons("55/0") -TEXT = source.labels("63/0") -Via4 = source.polygons("66/0") -Metal5 = source.polygons("67/0") -Metal5_pin = source.polygons("67/2") -Metal5_filler = source.polygons("67/22") -Metal5_slit = source.polygons("67/24") -TopVia1 = source.polygons("125/0") -TopMetal1 = source.polygons("126/0") -TopMetal1_pin = source.polygons("126/2") -TopMetal1_filler = source.polygons("126/22") -TopMetal1_slit = source.polygons("126/24") -Vmim = source.polygons("129/0") -TopVia2 = source.polygons("133/0") -TopMetal2 = source.polygons("134/0") -TopMetal2_pin = source.polygons("134/2") -TopMetal2_filler = source.polygons("134/22") -TopMetal2_slit = source.polygons("134/24") -ColWind = source.polygons("139/0") -LBE = source.polygons("157/0") -PEmWind = source.polygons("11/0") -PEmPoly = source.polygons("53/0") -LDMOS = source.polygons("57/0") -PBiWind = source.polygons("58/0") -Flash = source.polygons("71/0") -Activ_Act_a = Activ.ext_width(0.15.um) -Act_density = Activ.ext_or(Activ_filler) -Gat_density = GatPoly.ext_or(GatPoly_filler) -Cont_SQ = Cont.ext_rectangles(true, false, [["==", 0.16.um]], [["==", 0.16.um]], nil) -ContBar = Cont.ext_with_area([[">", (0.16*0.16).um2]]) -Metal1_Nslit = Metal1.ext_not(Metal1_slit) -Metal2_Nslit = Metal2.ext_not(Metal2_slit) -Act_Nsram = Activ.ext_not(SRAM) -GP_Nsram = GatPoly.ext_not(SRAM) -Metal3_Nslit = Metal3.ext_not(Metal3_slit) -Via1_edgC1_out = Via1.ext_not(EdgeSeal) -Via2_edgC1_out = Via2.ext_not(EdgeSeal) -Cont_outside_EdgeSeal = Cont.ext_outside(EdgeSeal) -ThickGateOx_TGO_f = ThickGateOx.ext_width(0.86.um, consider_intersecting_edges: false, polygon_output: true) -Via3_edgC1_out = Via3.ext_not(EdgeSeal) -Metal4_Nslit = Metal4.ext_not(Metal4_slit) -Via4_edgC1_out = Via4.ext_not(EdgeSeal) -Metal5_Nslit = Metal5.ext_not(Metal5_slit) -TopVia1_edgC1_out = TopVia1.ext_not(EdgeSeal) -TopMetal1_Nslit = TopMetal1.ext_not(TopMetal1_slit) -TopVia1_or_Vmim = TopVia1.ext_or(Vmim) -TopVia2_edgC1_out = TopVia2.ext_not(EdgeSeal) -TopMetal2_Nslit = TopMetal2.ext_not(TopMetal2_slit) -M1_density = Metal1.ext_or(Metal1_filler).ext_not(Metal1_slit) -M2_density = Metal2.ext_or(Metal2_filler).ext_not(Metal2_slit) -emi2Pin = Metal2_pin.ext_and(TRANS).ext_interacting_with_text(TEXT, "E") -M3_density = Metal3.ext_or(Metal3_filler).ext_not(Metal3_slit) -M4_density = Metal4.ext_or(Metal4_filler).ext_not(Metal4_slit) -M5_density = Metal5.ext_or(Metal5_filler).ext_not(Metal5_slit) -TM1_density = TopMetal1.ext_or(TopMetal1_filler).ext_not(TopMetal1_slit) -TM2_density = TopMetal2.ext_or(TopMetal2_filler).ext_not(TopMetal2_slit) -M1_Nsram = Metal1_Nslit.ext_not(SRAM) -M2_Nsram = Metal2_Nslit.ext_not(SRAM) -GP_Nsram_Gat_a = GP_Nsram.ext_width(0.13.um, consider_intersecting_edges: false, polygon_output: true) -GP_Nsram_Gat_b = GP_Nsram.ext_space(0.18.um, consider_intersecting_edges: false, polygon_output: true) -M3_Nsram = Metal3_Nslit.ext_not(SRAM) -M4_Nsram = Metal4_Nslit.ext_not(SRAM) -M5_Nsram = Metal5_Nslit.ext_not(SRAM) -transG2L = TRANS.ext_interacting_with_text(TEXT, "npn13G2L").ext_covering(emi2Pin) --> do - Activ_Act_a.dup -end.().output("Act.a", "Min. Activ width = 0.15") --> do - Act_Nsram.ext_space(0.21.um) -end.().output("Act.b", "Min. Activ space or notch = 0.21") - -if $densityRules - -> do - Act_density.ext_without_density(0.35 .. 0.55, 'll') - end.().output("AFil.g/g1", "Global Activ density [%] = 35.00 .. 55.00") - -> do - Act_density.ext_without_density(0.25 .. 0.65, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("AFil.g2/g3", "Activ coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 65.00") -end - --> do - ThickGateOx_TGO_f.dup -end.().output("TGO.f", "Min. ThickGateOx width = 0.86") --> do - GP_Nsram_Gat_a.dup -end.().output("Gat.a", "Min. GatPoly width = 0.13") --> do - GP_Nsram_Gat_b.dup -end.().output("Gat.b", "Min. GatPoly space or notch = 0.18") --> do - GP_Nsram.ext_separation(Act_Nsram, 0.07.um) -end.().output("Gat.d", "Min. GatPoly space to Activ = 0.07") - -if $densityRules - -> do - Gat_density.ext_with_density(0.0 .. 0.15, 'll') - end.().output("GFil.g", "Min. global GatPoly density [%] = 15.00") -end - --> do - Cont_outside_EdgeSeal.ext_not(ContBar.ext_or(Cont_SQ)) -end.().output("Cnt.a", "Min. and max. Cont width = 0.16") --> do - Cont_outside_EdgeSeal.ext_space(0.18.um, consider_intersecting_edges: false) -end.().output("Cnt.b", "Min. Cont space = 0.18") --> do - Metal1_Nslit.ext_width(0.16.um) -end.().output("M1.a", "Min. Metal1 width = 0.16") --> do - M1_Nsram.ext_space(0.18.um) -end.().output("M1.b", "Min. Metal1 space or notch = 0.18") - -if $densityRules - -> do - M1_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M1.j/k", "Global Metal1 density [%] = 35.0 .. 60.0") -end - --> do - Metal2_Nslit.ext_width(0.2.um) -end.().output("M2.a", "Min. Metal2 width = 0.20") --> do - M2_Nsram.ext_space(0.21.um) -end.().output("M2.b", "Min. Metal2 space or notch = 0.21") - -if $densityRules - -> do - M2_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M2.j/k", "Global Metal2 density [%] = 35.00 .. 60.00") -end - --> do - Metal3_Nslit.ext_width(0.2.um) -end.().output("M3.a", "Min. Metal3 width = 0.20") --> do - M3_Nsram.ext_space(0.21.um) -end.().output("M3.b", "Min. Metal3 space or notch = 0.21") - -if $densityRules - -> do - M3_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M3.j/k", "Global Metal3 density [%] = 35.00 .. 60.00") -end - --> do - Metal4_Nslit.ext_width(0.2.um) -end.().output("M4.a", "Min. Metal4 width = 0.20") --> do - M4_Nsram.ext_space(0.21.um) -end.().output("M4.b", "Min. Metal4 space or notch = 0.21") - -if $densityRules - -> do - M4_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M4.j/k", "Global Metal4 density [%] = 35.00 .. 60.00") -end - --> do - Metal5_Nslit.ext_width(0.2.um) -end.().output("M5.a", "Min. Metal5 width = 0.20") --> do - M5_Nsram.ext_space(0.21.um) -end.().output("M5.b", "Min. Metal5 space or notch = 0.21") - -if $densityRules - -> do - M5_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M5.j/k", "Global Metal5 density [%] = 35.00 .. 60.00") - -> do - M1_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M1Fil.h/k", "Metal1 and Metal1:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") - -> do - M2_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M2Fil.h/k", "Metal2 and Metal2:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") - -> do - M3_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M3Fil.h/k", "Metal3 and Metal3:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") - -> do - M4_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M4Fil.h/k", "Metal4 and Metal4:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") - -> do - M5_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M5Fil.h/k", "Metal5 and Metal5:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") -end - --> do - Via1_edgC1_out.ext_outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true) -end.().output("V1.a", "Min. and max. Via1 width = 0.19") --> do - Via1_edgC1_out.ext_space(0.22.um, consider_intersecting_edges: false) -end.().output("V1.b", "Min. Via1 space = 0.22") --> do - Via2_edgC1_out.ext_outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true) -end.().output("V2.a", "Min. and max. Via2 width = 0.19") --> do - Via2_edgC1_out.ext_space(0.22.um, consider_intersecting_edges: false) -end.().output("V2.b", "Min. Via2 space = 0.22") --> do - Via3_edgC1_out.ext_outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true) -end.().output("V3.a", "Min. and max. Via3 width = 0.19") --> do - Via3_edgC1_out.ext_space(0.22.um, consider_intersecting_edges: false) -end.().output("V3.b", "Min. Via3 space = 0.22") --> do - Via4_edgC1_out.ext_outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true) -end.().output("V4.a", "Min. and max. Via4 width = 0.19") --> do - Via4_edgC1_out.ext_space(0.22.um, consider_intersecting_edges: false) -end.().output("V4.b", "Min. Via4 space = 0.22") --> do - TopVia1_edgC1_out.ext_or(Vmim).ext_rectangles(false, false, [["==", 0.42.um]], [["==", 0.42.um]], nil, inverted: true) -end.().output("TV1.a", "Min. and max. TopVia1 width = 0.42") --> do - TopVia1_or_Vmim.ext_space(0.42.um) -end.().output("TV1.b", "Min. TopVia1 space = 0.42") --> do - TopMetal1_Nslit.ext_width(1.64.um) -end.().output("TM1.a", "Min. TopMetal1 width = 1.64") --> do - TopMetal1_Nslit.ext_space(1.64.um) -end.().output("TM1.b", "Min. TopMetal1 space or notch = 1.64") - -if $densityRules - -> do - TM1_density.ext_without_density(0.25 .. 0.7, 'll') - end.().output("TM1.c/d", "Global TopMetal1 density [%] = 25.00 .. 70.00") -end - --> do - TopVia2_edgC1_out.ext_rectangles(false, false, [["==", 0.9.um]], [["==", 0.9.um]], nil, inverted: true) -end.().output("TV2.a", "Min. and max. TopVia2 width = 0.90") --> do - TopVia2.ext_space(1.06.um) -end.().output("TV2.b", "Min. TopVia2 space = 1.06") --> do - TopMetal2_Nslit.ext_width(2.0.um) -end.().output("TM2.a", "Min. TopMetal2 width = 2.00") --> do - TopMetal2_Nslit.ext_space(2.0.um) -end.().output("TM2.b", "Min. TopMetal2 space or notch = 2.00") - -if $densityRules - -> do - TM2_density.ext_without_density(0.25 .. 0.7, 'll') - end.().output("TM2.c/d", "Global TopMetal2 density [%] = 25.00 .. 70.00") -end - --> do - Passiv.ext_width(2.1.um) -end.().output("Pas.a", "Min. Passiv width = 2.10") --> do - Passiv.ext_space(3.5.um) -end.().output("Pas.b", "Min. Passiv space or notch = 3.50") --> do - Activ_pin.ext_not(Activ) -end.().output("Pin.a", "Min. Activ enclosure of Activ:pin = 0.00") --> do - GatPoly_pin.ext_not(GatPoly) -end.().output("Pin.b", "Min. GatPoly enclosure of GatPoly:pin = 0.00") --> do - Metal1_pin.ext_not(Metal1_Nslit) -end.().output("Pin.e", "Min. Metal1 enclosure of Metal1:pin = 0.00") --> do - Metal2_pin.ext_not(Metal2_Nslit) -end.().output("Pin.f.M2", "Min. Metal2 enclosure of Metal2:pin = 0.00") --> do - Metal3_pin.ext_not(Metal3_Nslit) -end.().output("Pin.f.M3", "Min. Metal3 enclosure of Metal3:pin = 0.00") --> do - Metal4_pin.ext_not(Metal4_Nslit) -end.().output("Pin.f.M4", "Min. Metal4 enclosure of Metal4:pin = 0.00") --> do - Metal5_pin.ext_not(Metal5_Nslit) -end.().output("Pin.f.M5", "Min. Metal5 enclosure of Metal5:pin = 0.00") --> do - TopMetal1_pin.ext_not(TopMetal1_Nslit) -end.().output("Pin.g", "Min. TopMetal1 enclosure of TopMetal1:pin = 0.00") --> do - TopMetal2_pin.ext_not(TopMetal2_Nslit) -end.().output("Pin.h", "Min. TopMetal2 enclosure of TopMetal2:pin = 0.00") --> do - LBE.ext_width(100.0.um) -end.().output("LBE.a", "Min. LBE width = 100.00") --> do - LBE.sized(-1500.0.um/2.0, acute_limit).sized(1500.0.um/2.0, acute_limit) -end.().output("LBE.b", "Max. LBE width = 1500.00") --> do - LBE.ext_with_area([[">", 250000.0.um2]]) -end.().output("LBE.b1", "Max. LBE area (µm²) = 250000.00") --> do - LBE.ext_space(100.0.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true) -end.().output("LBE.c", "Min. LBE space or notch = 100.00") --> (;lbe_in_seal) do - lbe_in_seal = LBE.inside(EdgeSeal.holes.merge) - lbe_in_seal.ext_separation(EdgeSeal, 150.0.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true) -end.().output("LBE.d", "Min. LBE space to inner edge of EdgeSeal = 150.00") --> do - LBE.with_holes.dup -end.().output("LBE.h", "No LBE ring allowed") - -if $densityRules - -> do - LBE.ext_with_density(0.2 .. 1.0, 'll') - end.().output("LBE.i", "Max. global LBE density [%] = 20.00") -end - --> do - BiWind.dup -end.().output("forbidden.BiWind", "Forbidden drawn layer BiWind on GDS layer 3/0 = 3/0") --> do - PEmWind.dup -end.().output("forbidden.PEmWind", "Forbidden drawn layer PEmWind on GDS layer 11/0 = 11/0") --> do - BasPoly.dup -end.().output("forbidden.BasPoly", "Forbidden drawn layer BasPoly on GDS layer 13/0 = 13/0") --> do - DeepCo.dup -end.().output("forbidden.DeepCo", "Forbidden drawn layer DeepCo on GDS layer 35/0 = 35/0") --> do - PEmPoly.dup -end.().output("forbidden.PEmPoly", "Forbidden drawn layer PEmPoly on GDS layer 53/0 = 53/0") --> do - EmPoly.dup -end.().output("forbidden.EmPoly", "Forbidden gen./drawn layer EmPoly on GDS layer 53/0 = 53/0") --> do - LDMOS.dup -end.().output("forbidden.LDMOS", "Forbidden drawn layer LDMOS on GDS layer 57/0 = 57/0") --> do - PBiWind.dup -end.().output("forbidden.PBiWind", "Forbidden drawn layer PBiWind on GDS layer 58/0 = 58/0") --> do - Flash.dup -end.().output("forbidden.Flash", "Forbidden drawn layer Flash on GDS layer 71/0 = 71/0") --> do - ColWind.dup -end.().output("forbidden.ColWind", "Forbidden drawn layer ColWind on GDS layer 139/0 = 139/0") - -puts("Number of DRC errors: #{$drc_error_count}") -puts("Runtime in seconds: %.1f" % [Time.now - $start_time]) - - diff --git a/flow/platforms/ihp-sg13g2/drc/sg13g2_minimal_drc.py b/flow/platforms/ihp-sg13g2/drc/sg13g2_minimal_drc.py new file mode 100644 index 0000000000..55d400b1a0 --- /dev/null +++ b/flow/platforms/ihp-sg13g2/drc/sg13g2_minimal_drc.py @@ -0,0 +1,1138 @@ +# Copyright 2025 IHP 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. +"""IHP SG13G2 minimal DRC deck, run by flow/util/klayout_drc.py on the +klayout module. + +Translated line by line from the KLayout DRC language deck +sg13g2_minimal.lydrc, including the ext_* layer methods it adds to the +DRC language, which this deck adds to klayout_drc.Layer. +""" + +import math + +import klayout.db as db + +from klayout_drc import ( + Layer, + _Option, + acute_limit, + angle_limit, + both, + euclidian, + ortho, + padding_ignore, + tile_boundary, + tile_count, + tile_origin, + tile_size, + tile_step, + um, +) + +DESCRIPTION = "design rules: sg13g2_minimal" +MODE = "deep" +THREADS = 1 + +DENSITY_RULES = True + +CRON = db.CompoundRegionOperationNode + + +def _ip(edge_pair): + """The point the two edges of an edge pair share, or None.""" + if ( + edge_pair.first.p1 == edge_pair.second.p1 + or edge_pair.first.p1 == edge_pair.second.p2 + ): + return edge_pair.first.p1 + if ( + edge_pair.first.p2 == edge_pair.second.p1 + or edge_pair.first.p2 == edge_pair.second.p2 + ): + return edge_pair.first.p2 + return None + + +def _angle(edge_pair): + ep = edge_pair.normalized() + v1 = ep.first.swapped_points().d() + v2 = ep.second.d() + return math.acos((v1.sprod(v2)) / (v1.length() * v2.length())) / math.pi * 180 + + +class AbuttingEdges(db.EdgePairToEdgeOperator): + def __init__(self): + self.is_isotropic_and_scale_invariant() + + def process(self, edge_pair): + if edge_pair.first.coincident(edge_pair.second): + if edge_pair.first.length() < edge_pair.second.length(): + return [edge_pair.first] + return [edge_pair.second] + return [] + + +class IntersectingEdgeErrorFilter(db.EdgePairOperator): + def __init__(self, abutting_edges): + self.abutting_edges = abutting_edges + + def process(self, edge_pair): + ip = _ip(edge_pair) + if ip is not None and not edge_pair.first.coincident(edge_pair.second): + edges = db.Edges([edge_pair.first, edge_pair.second]) + region = self.abutting_edges.extents(1) + if edges.interacting(region).count() == 2: + if _angle(edge_pair) >= 90: + return [] + return [edge_pair] + + +class IntersectingEdgeErrorAngleFilter(db.EdgePairOperator): + def __init__(self, min_angle, max_angle, include_min_angle, include_max_angle): + self.is_isotropic_and_scale_invariant() + self.min_angle = min_angle + (-1e-6 if include_min_angle else 1e-6) + self.max_angle = max_angle + (1e-6 if include_max_angle else -1e-6) + + def process(self, edge_pair): + if _ip(edge_pair) is not None: + angle = _angle(edge_pair) + if angle < self.min_angle or angle > self.max_angle: + return [] + return [edge_pair] + + +def find_intersecting_edges_errors( + deck, + dbu_value, + error_edge_pairs_90, + error_edge_pairs_180, + error_edge_pairs_90_180=None, + inverse_error_edge_pairs_90=None, + inverse_error_edge_pairs_180=None, + consider_intersecting_edges=False, + consider_touch_points=False, + ignore_non_axis_aligned_edges=False, + min_angle=0, + max_angle=90, + include_min_angle=True, + include_max_angle=False, +): + errors_ep = db.EdgePairs() + touch_point_errors_ep = db.EdgePairs() + abutting_edges_errors_ep = db.EdgePairs() + intersecting_edges_error_candidates = {} + no_touch_point_error = {} + for edge_pair in error_edge_pairs_90.data.each(): + ip = _ip(edge_pair) + if ( + ip is not None + and not edge_pair.first.is_degenerate() + and not edge_pair.second.is_degenerate() + ): + if ( + edge_pair.first.contains(edge_pair.second.p1) + and edge_pair.first.contains(edge_pair.second.p2) + ) or ( + edge_pair.second.contains(edge_pair.first.p1) + and edge_pair.second.contains(edge_pair.first.p2) + ): + no_touch_point_error[ip] = True + if consider_intersecting_edges or consider_touch_points: + touch_point_candidates = {} + touch_point_errors = {} + if inverse_error_edge_pairs_90 and inverse_error_edge_pairs_180: + for edge_pair in ( + inverse_error_edge_pairs_90 + inverse_error_edge_pairs_180 + ).data.each(): + ip = edge_pair.first.intersection_point(edge_pair.second) + if ip is not None: + if ip in touch_point_candidates: + touch_point_errors[ip] = True + del touch_point_candidates[ip] + elif not no_touch_point_error.get(ip): + touch_point_candidates[ip] = edge_pair + touch_point_candidates = {} + if not error_edge_pairs_90_180: + error_edge_pairs_90_180 = error_edge_pairs_90 + error_edge_pairs_180 + for edge_pair in error_edge_pairs_90_180.data.each(): + ip = _ip(edge_pair) + if ( + ip is None + and edge_pair.first.intersection_point(edge_pair.second) is not None + ): + abutting_edges_errors_ep.insert(edge_pair) + if ( + ip + and not edge_pair.first.is_degenerate() + and not edge_pair.second.is_degenerate() + ): + intersecting_edges_error_candidates[ip] = edge_pair + if touch_point_errors.get(ip): + touch_point_errors_ep.insert(edge_pair) + del intersecting_edges_error_candidates[ip] + elif ip in touch_point_candidates: + touch_point_errors_ep.insert(edge_pair) + touch_point_errors_ep.insert(touch_point_candidates[ip]) + del touch_point_candidates[ip] + del intersecting_edges_error_candidates[ip] + elif not no_touch_point_error.get(ip): + touch_point_candidates[ip] = edge_pair + if consider_intersecting_edges: + errors_ep = db.EdgePairs(list(intersecting_edges_error_candidates.values())) + errors_ep.process( + IntersectingEdgeErrorAngleFilter( + min_angle, max_angle, include_min_angle, include_max_angle + ) + ) + if min_angle == 0 and include_min_angle: + errors_ep = errors_ep + abutting_edges_errors_ep + if ignore_non_axis_aligned_edges: + errors_ep = errors_ep.with_angle_both(db.Edges.OrthoEdges, False) + if consider_touch_points: + errors_ep = errors_ep + touch_point_errors_ep + return Layer(deck, errors_ep) + + +class _MinCoherence: + """min_coherence set to a value on layers for a block, then restored.""" + + def __init__(self, value, *layers): + self.value = value + self.layers = layers + + def __enter__(self): + self.saved = [lay.data.min_coherence for lay in self.layers] + for lay in self.layers: + lay.data.min_coherence = self.value + + def __exit__(self, *exc): + for lay, s in zip(self.layers, self.saved): + lay.data.min_coherence = s + + +def _dbu_value(deck, value): + if isinstance(value, float): + return round(value / deck.dbu) + return value + + +def ext_merged(self, min_coherence, overlap_count): + return self._new(self.data.merged(min_coherence, overlap_count)) + + +def ext_merge(self, min_coherence, overlap_count): + self.data.merge(min_coherence, overlap_count) + return self + + +def ext_and(self, other): + with _MinCoherence(True, self, other): + return self & other + + +def ext_with_area(self, constraint): + lower_bound = upper_bound = output_layer = None + with _MinCoherence(True, self): + d2 = self.deck.dbu**2 + for relation, value in constraint: + if relation == ">": + lower_bound = value + d2 + elif relation == "<": + upper_bound = value + elif relation == "==": + output_layer = self.with_area(value) + elif relation == "!=": + output_layer = self.without_area(value) + elif relation == ">=": + lower_bound = value + elif relation == "<=": + upper_bound = value + d2 + else: + raise ValueError("invalid expression") + if lower_bound is not None or upper_bound is not None: + output_layer = self.with_area(lower_bound, upper_bound) + return output_layer + + +def ext_covering(self, other): + with _MinCoherence(True, self, other): + return self.covering(other.inside(self)) + + +def ext_separation_at_intersecting_edges( + self, + other, + value, + metric=None, + consider_intersecting_edges=False, + consider_touch_points=False, + ignore_non_axis_aligned_edges=False, + min_angle=0, + max_angle=90, + include_min_angle=True, + include_max_angle=False, + polygons=False, +): + metric = _metric(metric) + with _MinCoherence(True, self, other): + dbu_value = _dbu_value(self.deck, value) + sd, od = self.data, other.data + error_edge_pairs_90 = self._new( + sd.separation_check(od, dbu_value, False, metric, 90, 1, None) + ) + error_edge_pairs_90_180 = self._new( + sd.separation_check(od, dbu_value, False, metric, 180) + ) + width_error_edge_pairs_90 = self._new( + sd.width_check(dbu_value, False, metric, 90, 1, None) + + od.width_check(dbu_value, False, metric, 90, 1, None) + ) + width_error_edge_pairs_180 = self._new( + sd.width_check(dbu_value, False, metric, 180, None, 1) + + od.width_check(dbu_value, False, metric, 180, None, 1) + ) + errors = find_intersecting_edges_errors( + self.deck, + dbu_value, + error_edge_pairs_90, + None, + error_edge_pairs_90_180, + width_error_edge_pairs_90, + width_error_edge_pairs_180, + consider_intersecting_edges=consider_intersecting_edges, + consider_touch_points=consider_touch_points, + ignore_non_axis_aligned_edges=ignore_non_axis_aligned_edges, + min_angle=min_angle, + max_angle=max_angle, + include_min_angle=include_min_angle, + include_max_angle=include_max_angle, + ) + if polygons: + errors = errors.polygons().ext_merge(True, 0) + return errors + + +def _metric(metric): + """A metric option of klayout_drc, or a db.Region metric constant.""" + if metric is None: + return db.Region.Euclidian + if hasattr(metric, "kind"): + return getattr(db.Region, metric.value) + return metric + + +def ext_separation( + self, + other, + value, + metric=euclidian, + consider_intersecting_edges=True, + consider_touch_points=True, + consider_overlaps=False, + ignore_non_axis_aligned_edges=False, + inside_edges_are_errors=False, + min_angle=0, + max_angle=90, + include_min_angle=True, + include_max_angle=False, + polygon_output=False, +): + with _MinCoherence(True, self, other): + if consider_overlaps: + raise NotImplementedError("consider_overlaps is not used by this deck") + limit = max_angle + if include_max_angle: + limit += 1e-6 + output_layer = self.separation(other, value, metric, angle_limit(limit)) + if not consider_intersecting_edges and not consider_touch_points: + output_layer = output_layer.without_distance(0) + if ignore_non_axis_aligned_edges: + output_layer = output_layer.with_angle(ortho, both) + elif (consider_intersecting_edges != consider_touch_points) or not ( + min_angle == 0 + and max_angle == 90 + and include_min_angle + and not include_max_angle + ): + intersecting_edges_errors = output_layer.with_distance(0).edges() + candidate_layer1 = self.interacting(intersecting_edges_errors) + candidate_layer2 = other.interacting(intersecting_edges_errors) + output_layer = output_layer.without_distance(0) + output_layer = ( + output_layer + + candidate_layer1.ext_separation_at_intersecting_edges( + candidate_layer2, + value, + metric, + consider_intersecting_edges, + consider_touch_points, + ignore_non_axis_aligned_edges, + min_angle, + max_angle, + include_min_angle, + include_max_angle, + False, + ) + ) + if inside_edges_are_errors: + raise NotImplementedError( + "inside_edges_are_errors is not used by this deck" + ) + if polygon_output: + output_layer = output_layer.polygons().ext_merge(True, 0) + return output_layer + + +def ext_not(self, other): + with _MinCoherence(True, self, other): + return self - other + + +def ext_or(self, other, *further_layers): + with _MinCoherence(True, self, other): + output_layer = self.join(other) + for further_layer in further_layers: + with _MinCoherence(True, further_layer): + output_layer = output_layer.join(further_layer) + return output_layer + + +def _corners_count(primary, angle): + """(corners == angle).count == 4, or corners.count == 4 without angle.""" + lo, hi = (-180.0, 180.0) if angle is None else (angle, angle) + corners = CRON.new_corners_as_rectangles( + primary, lo, True, hi, True, 2, False, False + ) + return CRON.new_count_filter(corners, False, 4, 5) + + +def ext_rectangles( + self, + axis_aligned=False, + use_bbox=False, + constraint1=None, + constraint2=None, + aspect_ratio_constraint=None, + inverted=False, +): + """Rectangles, or squares of a side, as universal DRC expressions.""" + if ( + ( + constraint1 + and (not constraint2 or len(constraint1) > 1 or constraint1[0][0] != "==") + ) + or (constraint2 and (len(constraint2) > 1 or constraint2[0][0] != "==")) + or (constraint1 and constraint2 and constraint1[0][1] != constraint2[0][1]) + ): + raise ValueError("ext_rectangle: unsupported options") + if use_bbox: + raise NotImplementedError("use_bbox is not used by this deck") + with _MinCoherence(True, self): + primary = CRON.new_primary() + if axis_aligned: + shape_filter = CRON.new_rectangle_filter(primary, False, False) + else: + shape_filter = CRON.new_logical_boolean( + CRON.LogicalOp.LogAnd, + False, + [_corners_count(primary, 270.0), _corners_count(primary, None)], + ) + if constraint1: + side = self.deck.v(constraint1[0][1]) + lengths = CRON.new_edge_length_filter( + CRON.new_edges(primary), False, side, side + 1 + ) + square_filter = CRON.new_count_filter(lengths, False, 4, 5) + shape_filter = CRON.new_logical_boolean( + CRON.LogicalOp.LogAnd, False, [shape_filter, square_filter] + ) + if inverted: + empty = CRON.new_empty(CRON.ResultType.Region) + shape_filter = CRON.new_case([shape_filter, empty, CRON.new_primary()]) + return self._new(self.data.complex_op(shape_filter, db.Region.IgnoreProperties)) + + +def _single_at_intersecting_edges( + self, + main, + inverse, + value, + metric, + consider_intersecting_edges, + consider_touch_points, + ignore_non_axis_aligned_edges, + min_angle, + max_angle, + include_min_angle, + include_max_angle, + polygons, +): + """ext_space_at_intersecting_edges and ext_width_at_intersecting_edges: + the main check and the inverse one, at 90 and 180 degrees.""" + metric = _metric(metric) + with _MinCoherence(True, self): + dbu_value = _dbu_value(self.deck, value) + m = getattr(self.data, main + "_check") + i = getattr(self.data, inverse + "_check") + errors = find_intersecting_edges_errors( + self.deck, + dbu_value, + self._new(m(dbu_value, False, metric, 90, 1, None)), + self._new(m(dbu_value, False, metric, 180, None, 1)), + None, + self._new(i(dbu_value, False, metric, 90, 1, None)), + self._new(i(dbu_value, False, metric, 180, None, 1)), + consider_intersecting_edges=consider_intersecting_edges, + consider_touch_points=consider_touch_points, + ignore_non_axis_aligned_edges=ignore_non_axis_aligned_edges, + min_angle=min_angle, + max_angle=max_angle, + include_min_angle=include_min_angle, + include_max_angle=include_max_angle, + ) + if polygons: + errors = errors.polygons().ext_merge(True, 0) + return errors + + +def ext_space_at_intersecting_edges(self, value, metric=None, *args): + return _single_at_intersecting_edges(self, "space", "width", value, metric, *args) + + +def ext_width_at_intersecting_edges(self, value, metric=None, *args): + return _single_at_intersecting_edges(self, "width", "space", value, metric, *args) + + +def _single( + self, + check, + min_coherence, + value, + metric, + consider_intersecting_edges, + consider_touch_points, + ignore_non_axis_aligned_edges, + min_angle, + max_angle, + include_min_angle, + include_max_angle, + polygon_output, +): + """ext_space and ext_width.""" + with _MinCoherence(min_coherence, self): + output_layer = getattr(self, check)(value, metric, angle_limit(max_angle)) + if not consider_intersecting_edges and not consider_touch_points: + output_layer = output_layer.with_distance(1, None) + if ignore_non_axis_aligned_edges: + output_layer = output_layer.with_angle(ortho, both) + elif consider_intersecting_edges != consider_touch_points: + candidate_layer = self.interacting(output_layer.with_distance(0).edges()) + output_layer = output_layer.with_distance(1, None) + output_layer = output_layer + getattr( + candidate_layer, "ext_%s_at_intersecting_edges" % check + )( + value, + metric, + consider_intersecting_edges, + consider_touch_points, + ignore_non_axis_aligned_edges, + min_angle, + max_angle, + include_min_angle, + include_max_angle, + False, + ) + if polygon_output: + output_layer = output_layer.polygons().ext_merge(True, 0) + return output_layer + + +def ext_space( + self, + value, + metric=euclidian, + consider_intersecting_edges=True, + consider_touch_points=True, + ignore_non_axis_aligned_edges=False, + min_angle=0, + max_angle=90, + include_min_angle=True, + include_max_angle=False, + polygon_output=False, +): + return _single( + self, + "space", + True, + value, + metric, + consider_intersecting_edges, + consider_touch_points, + ignore_non_axis_aligned_edges, + min_angle, + max_angle, + include_min_angle, + include_max_angle, + polygon_output, + ) + + +def ext_width( + self, + value, + metric=euclidian, + consider_intersecting_edges=True, + consider_touch_points=True, + ignore_non_axis_aligned_edges=False, + min_angle=0, + max_angle=90, + include_min_angle=True, + include_max_angle=False, + polygon_output=False, +): + return _single( + self, + "width", + False, + value, + metric, + consider_intersecting_edges, + consider_touch_points, + ignore_non_axis_aligned_edges, + min_angle, + max_angle, + include_min_angle, + include_max_angle, + polygon_output, + ) + + +def ext_interacting_with_text(self, text_layer, text): + initial_merged_semantics = self.data.merged_semantics + self.data.merged_semantics = False + result = self.interacting(text_layer.texts(text)) + self.data.merged_semantics = initial_merged_semantics + return result + + +def _ext_with_density(self, inverse, value_range, *args, tiles_stay_inside=False): + method = "without_density" if inverse else "with_density" + deck = self.deck + if self.is_merged(): + merged_layer = self + else: + merged_layer = self.ext_merged(True, 0) + origin = "cc" + size = step = None + arguments = [value_range] + for a in args: + if isinstance(a, str): + origin = a + elif a.kind == "tile_size": + size = a.value + arguments.append(a) + elif a.kind == "tile_step": + step = a.value + arguments.append(a) + else: + raise ValueError("argument error") + bbox = Layer(deck, db.Region(deck.top.bbox())).bbox() + if origin == "ll": + origin_x = bbox.left + origin_y = bbox.bottom + if size and step and (size[0] != step[0] or size[1] != step[1]): + origin_x = bbox.left + (size[0] - step[0]) / 2 + origin_y = bbox.bottom + (size[1] - step[1]) / 2 + origin_xy = (origin_x, origin_y) + arguments.append(tile_origin(*origin_xy)) + elif origin == "cc": + origin_xy = (bbox.center().x, bbox.center().y) + else: + raise ValueError("Unknown origin: 'cc' or 'll' expected") + + def run(*extra): + # A later tile_origin overrides one in arguments, as in the DRC + # language + return getattr(merged_layer, method)(*(arguments + list(extra))) + + def itype(box): + return box.to_itype(deck.dbu) + + if size: + boundary_layer = Layer(deck, db.Region(itype(bbox))) + boundary = tile_boundary(boundary_layer) + if tiles_stay_inside: + if origin != "ll": + raise ValueError("Option 'tiles_stay_inside' requires origin='ll'") + tile_width, tile_height = size + enlarged_bbox = bbox.dup() + if tile_width > bbox.width(): + enlarged_bbox.right = bbox.left + tile_width + if tile_height > bbox.height(): + enlarged_bbox.top = bbox.bottom + tile_height + enlarged_boundary_layer = Layer(deck, db.Region(itype(enlarged_bbox))) + step_x, step_y = step if step else (tile_width, tile_height) + count_x = max(1, 1 + math.floor((bbox.width() - tile_width) / step_x)) + count_y = max(1, 1 + math.floor((bbox.height() - tile_height) / step_y)) + if count_x == 1 and count_y == 1: + count = tile_count(count_x, 2) + else: + count = tile_count(count_x, count_y) + result = run(count, boundary, padding_ignore) + need_top_row = need_right_column = False + if bbox.height() > tile_height + (count_y - 1) * step_y: + need_top_row = True + origin2 = tile_origin( + origin_xy[0], origin_xy[1] + bbox.height() - tile_height + ) + result += run( + origin2, tile_count(max(2, count_x), 1), boundary, padding_ignore + ) + if bbox.width() > tile_width + (count_x - 1) * step_x: + need_right_column = True + origin2 = tile_origin( + origin_xy[0] + bbox.width() - tile_width, origin_xy[1] + ) + result += run( + origin2, tile_count(1, max(2, count_y)), boundary, padding_ignore + ) + if (need_top_row and bbox.width() > tile_width) or ( + need_right_column and bbox.height() > tile_height + ): + origin2 = tile_origin( + origin_xy[0] + bbox.width() - tile_width, + origin_xy[1] + bbox.height() - tile_height, + ) + result += run(origin2, tile_count(1, 2), boundary, padding_ignore) + result.raw().select_inside(enlarged_boundary_layer) + else: + result = run(boundary, padding_ignore) + return result.and_(boundary_layer) + size = _size_option(bbox.width(), bbox.height()) + count = tile_count(1, 3) + enlarged_bbox = itype(bbox.enlarged(1.1)) + boundary = tile_boundary(Layer(deck, db.Region(enlarged_bbox))) + result = run(size, count, boundary, padding_ignore) + return result.raw().overlapping(Layer(deck, db.Region(itype(bbox)))) + + +def _size_option(w, h): + """A tile size in microns, as DRCTileSize.new(w, h).""" + return _Option("tile_size", (w, h)) + + +def ext_with_density(self, *args, **kargs): + return _ext_with_density(self, False, *args, **kargs) + + +def ext_without_density(self, *args, **kargs): + return _ext_with_density(self, True, *args, **kargs) + + +def ext_outside(self, other): + output_layer = self.dup().raw().outside(other) + output_layer.data.merged_semantics = True + return output_layer + + +for _f in ( + ext_merged, + ext_merge, + ext_and, + ext_with_area, + ext_covering, + ext_separation_at_intersecting_edges, + ext_separation, + ext_not, + ext_or, + ext_rectangles, + ext_space_at_intersecting_edges, + ext_space, + ext_width_at_intersecting_edges, + ext_width, + ext_interacting_with_text, + ext_with_density, + ext_without_density, + ext_outside, +): + setattr(Layer, _f.__name__, _f) + + +def drc(deck): + + Activ = deck.polygons("1/0") + Activ_pin = deck.polygons("1/2") + Activ_filler = deck.polygons("1/22") + BiWind = deck.polygons("3/0") + GatPoly = deck.polygons("5/0") + GatPoly_pin = deck.polygons("5/2") + GatPoly_filler = deck.polygons("5/22") + Cont = deck.polygons("6/0") + Metal1 = deck.polygons("8/0") + Metal1_pin = deck.polygons("8/2") + Metal1_filler = deck.polygons("8/22") + Metal1_slit = deck.polygons("8/24") + Passiv = deck.polygons("9/0") + Metal2 = deck.polygons("10/0") + Metal2_pin = deck.polygons("10/2") + Metal2_filler = deck.polygons("10/22") + Metal2_slit = deck.polygons("10/24") + BasPoly = deck.polygons("13/0") + Via1 = deck.polygons("19/0") + SRAM = deck.polygons("25/0") + TRANS = deck.polygons("26/0") + Via2 = deck.polygons("29/0") + Metal3 = deck.polygons("30/0") + Metal3_pin = deck.polygons("30/2") + Metal3_filler = deck.polygons("30/22") + Metal3_slit = deck.polygons("30/24") + DeepCo = deck.polygons("35/0") + EdgeSeal = deck.polygons("39/0") + ThickGateOx = deck.polygons("44/0") + Via3 = deck.polygons("49/0") + Metal4 = deck.polygons("50/0") + Metal4_pin = deck.polygons("50/2") + Metal4_filler = deck.polygons("50/22") + Metal4_slit = deck.polygons("50/24") + EmPoly = deck.polygons("55/0") + TEXT = deck.labels("63/0") + Via4 = deck.polygons("66/0") + Metal5 = deck.polygons("67/0") + Metal5_pin = deck.polygons("67/2") + Metal5_filler = deck.polygons("67/22") + Metal5_slit = deck.polygons("67/24") + TopVia1 = deck.polygons("125/0") + TopMetal1 = deck.polygons("126/0") + TopMetal1_pin = deck.polygons("126/2") + TopMetal1_filler = deck.polygons("126/22") + TopMetal1_slit = deck.polygons("126/24") + Vmim = deck.polygons("129/0") + TopVia2 = deck.polygons("133/0") + TopMetal2 = deck.polygons("134/0") + TopMetal2_pin = deck.polygons("134/2") + TopMetal2_filler = deck.polygons("134/22") + TopMetal2_slit = deck.polygons("134/24") + ColWind = deck.polygons("139/0") + LBE = deck.polygons("157/0") + PEmWind = deck.polygons("11/0") + PEmPoly = deck.polygons("53/0") + LDMOS = deck.polygons("57/0") + PBiWind = deck.polygons("58/0") + Flash = deck.polygons("71/0") + Activ_Act_a = Activ.ext_width(um(0.15)) + Act_density = Activ.ext_or(Activ_filler) + Gat_density = GatPoly.ext_or(GatPoly_filler) + Cont_SQ = Cont.ext_rectangles( + True, False, [["==", um(0.16)]], [["==", um(0.16)]], None + ) + ContBar = Cont.ext_with_area([[">", (0.16 * 0.16)]]) + Metal1_Nslit = Metal1.ext_not(Metal1_slit) + Metal2_Nslit = Metal2.ext_not(Metal2_slit) + Act_Nsram = Activ.ext_not(SRAM) + GP_Nsram = GatPoly.ext_not(SRAM) + Metal3_Nslit = Metal3.ext_not(Metal3_slit) + Via1_edgC1_out = Via1.ext_not(EdgeSeal) + Via2_edgC1_out = Via2.ext_not(EdgeSeal) + Cont_outside_EdgeSeal = Cont.ext_outside(EdgeSeal) + ThickGateOx_TGO_f = ThickGateOx.ext_width( + um(0.86), consider_intersecting_edges=False, polygon_output=True + ) + Via3_edgC1_out = Via3.ext_not(EdgeSeal) + Metal4_Nslit = Metal4.ext_not(Metal4_slit) + Via4_edgC1_out = Via4.ext_not(EdgeSeal) + Metal5_Nslit = Metal5.ext_not(Metal5_slit) + TopVia1_edgC1_out = TopVia1.ext_not(EdgeSeal) + TopMetal1_Nslit = TopMetal1.ext_not(TopMetal1_slit) + TopVia1_or_Vmim = TopVia1.ext_or(Vmim) + TopVia2_edgC1_out = TopVia2.ext_not(EdgeSeal) + TopMetal2_Nslit = TopMetal2.ext_not(TopMetal2_slit) + M1_density = Metal1.ext_or(Metal1_filler).ext_not(Metal1_slit) + M2_density = Metal2.ext_or(Metal2_filler).ext_not(Metal2_slit) + emi2Pin = Metal2_pin.ext_and(TRANS).ext_interacting_with_text(TEXT, "E") + M3_density = Metal3.ext_or(Metal3_filler).ext_not(Metal3_slit) + M4_density = Metal4.ext_or(Metal4_filler).ext_not(Metal4_slit) + M5_density = Metal5.ext_or(Metal5_filler).ext_not(Metal5_slit) + TM1_density = TopMetal1.ext_or(TopMetal1_filler).ext_not(TopMetal1_slit) + TM2_density = TopMetal2.ext_or(TopMetal2_filler).ext_not(TopMetal2_slit) + M1_Nsram = Metal1_Nslit.ext_not(SRAM) + M2_Nsram = Metal2_Nslit.ext_not(SRAM) + GP_Nsram_Gat_a = GP_Nsram.ext_width( + um(0.13), consider_intersecting_edges=False, polygon_output=True + ) + GP_Nsram_Gat_b = GP_Nsram.ext_space( + um(0.18), consider_intersecting_edges=False, polygon_output=True + ) + M3_Nsram = Metal3_Nslit.ext_not(SRAM) + M4_Nsram = Metal4_Nslit.ext_not(SRAM) + M5_Nsram = Metal5_Nslit.ext_not(SRAM) + transG2L = TRANS.ext_interacting_with_text(TEXT, "npn13G2L").ext_covering(emi2Pin) + Activ_Act_a.dup().output("Act.a", "Min. Activ width = 0.15") + Act_Nsram.ext_space(um(0.21)).output("Act.b", "Min. Activ space or notch = 0.21") + + if DENSITY_RULES: + Act_density.ext_without_density((0.35, 0.55), "ll").output( + "AFil.g/g1", "Global Activ density [%] = 35.00 .. 55.00" + ) + Act_density.ext_without_density( + (0.25, 0.65), + "ll", + tile_size(deck, um(800.0)), + tile_step(deck, um(400.0)), + tiles_stay_inside=True, + ).output( + "AFil.g2/g3", + "Activ coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 65.00", + ) + + ThickGateOx_TGO_f.dup().output("TGO.f", "Min. ThickGateOx width = 0.86") + GP_Nsram_Gat_a.dup().output("Gat.a", "Min. GatPoly width = 0.13") + GP_Nsram_Gat_b.dup().output("Gat.b", "Min. GatPoly space or notch = 0.18") + GP_Nsram.ext_separation(Act_Nsram, um(0.07)).output( + "Gat.d", "Min. GatPoly space to Activ = 0.07" + ) + + if DENSITY_RULES: + Gat_density.ext_with_density((0.0, 0.15), "ll").output( + "GFil.g", "Min. global GatPoly density [%] = 15.00" + ) + + Cont_outside_EdgeSeal.ext_not(ContBar.ext_or(Cont_SQ)).output( + "Cnt.a", "Min. and max. Cont width = 0.16" + ) + Cont_outside_EdgeSeal.ext_space(um(0.18), consider_intersecting_edges=False).output( + "Cnt.b", "Min. Cont space = 0.18" + ) + Metal1_Nslit.ext_width(um(0.16)).output("M1.a", "Min. Metal1 width = 0.16") + M1_Nsram.ext_space(um(0.18)).output("M1.b", "Min. Metal1 space or notch = 0.18") + + if DENSITY_RULES: + M1_density.ext_without_density((0.35, 0.6), "ll").output( + "M1.j/k", "Global Metal1 density [%] = 35.0 .. 60.0" + ) + + Metal2_Nslit.ext_width(um(0.2)).output("M2.a", "Min. Metal2 width = 0.20") + M2_Nsram.ext_space(um(0.21)).output("M2.b", "Min. Metal2 space or notch = 0.21") + + if DENSITY_RULES: + M2_density.ext_without_density((0.35, 0.6), "ll").output( + "M2.j/k", "Global Metal2 density [%] = 35.00 .. 60.00" + ) + + Metal3_Nslit.ext_width(um(0.2)).output("M3.a", "Min. Metal3 width = 0.20") + M3_Nsram.ext_space(um(0.21)).output("M3.b", "Min. Metal3 space or notch = 0.21") + + if DENSITY_RULES: + M3_density.ext_without_density((0.35, 0.6), "ll").output( + "M3.j/k", "Global Metal3 density [%] = 35.00 .. 60.00" + ) + + Metal4_Nslit.ext_width(um(0.2)).output("M4.a", "Min. Metal4 width = 0.20") + M4_Nsram.ext_space(um(0.21)).output("M4.b", "Min. Metal4 space or notch = 0.21") + + if DENSITY_RULES: + M4_density.ext_without_density((0.35, 0.6), "ll").output( + "M4.j/k", "Global Metal4 density [%] = 35.00 .. 60.00" + ) + + Metal5_Nslit.ext_width(um(0.2)).output("M5.a", "Min. Metal5 width = 0.20") + M5_Nsram.ext_space(um(0.21)).output("M5.b", "Min. Metal5 space or notch = 0.21") + + if DENSITY_RULES: + M5_density.ext_without_density((0.35, 0.6), "ll").output( + "M5.j/k", "Global Metal5 density [%] = 35.00 .. 60.00" + ) + M1_density.ext_without_density( + (0.25, 0.75), + "ll", + tile_size(deck, um(800.0)), + tile_step(deck, um(400.0)), + tiles_stay_inside=True, + ).output( + "M1Fil.h/k", + "Metal1 and Metal1:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00", + ) + M2_density.ext_without_density( + (0.25, 0.75), + "ll", + tile_size(deck, um(800.0)), + tile_step(deck, um(400.0)), + tiles_stay_inside=True, + ).output( + "M2Fil.h/k", + "Metal2 and Metal2:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00", + ) + M3_density.ext_without_density( + (0.25, 0.75), + "ll", + tile_size(deck, um(800.0)), + tile_step(deck, um(400.0)), + tiles_stay_inside=True, + ).output( + "M3Fil.h/k", + "Metal3 and Metal3:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00", + ) + M4_density.ext_without_density( + (0.25, 0.75), + "ll", + tile_size(deck, um(800.0)), + tile_step(deck, um(400.0)), + tiles_stay_inside=True, + ).output( + "M4Fil.h/k", + "Metal4 and Metal4:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00", + ) + M5_density.ext_without_density( + (0.25, 0.75), + "ll", + tile_size(deck, um(800.0)), + tile_step(deck, um(400.0)), + tiles_stay_inside=True, + ).output( + "M5Fil.h/k", + "Metal5 and Metal5:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00", + ) + + Via1_edgC1_out.ext_outside(transG2L).ext_rectangles( + False, False, [["==", um(0.19)]], [["==", um(0.19)]], None, inverted=True + ).output("V1.a", "Min. and max. Via1 width = 0.19") + Via1_edgC1_out.ext_space(um(0.22), consider_intersecting_edges=False).output( + "V1.b", "Min. Via1 space = 0.22" + ) + Via2_edgC1_out.ext_outside(transG2L).ext_rectangles( + False, False, [["==", um(0.19)]], [["==", um(0.19)]], None, inverted=True + ).output("V2.a", "Min. and max. Via2 width = 0.19") + Via2_edgC1_out.ext_space(um(0.22), consider_intersecting_edges=False).output( + "V2.b", "Min. Via2 space = 0.22" + ) + Via3_edgC1_out.ext_outside(transG2L).ext_rectangles( + False, False, [["==", um(0.19)]], [["==", um(0.19)]], None, inverted=True + ).output("V3.a", "Min. and max. Via3 width = 0.19") + Via3_edgC1_out.ext_space(um(0.22), consider_intersecting_edges=False).output( + "V3.b", "Min. Via3 space = 0.22" + ) + Via4_edgC1_out.ext_outside(transG2L).ext_rectangles( + False, False, [["==", um(0.19)]], [["==", um(0.19)]], None, inverted=True + ).output("V4.a", "Min. and max. Via4 width = 0.19") + Via4_edgC1_out.ext_space(um(0.22), consider_intersecting_edges=False).output( + "V4.b", "Min. Via4 space = 0.22" + ) + TopVia1_edgC1_out.ext_or(Vmim).ext_rectangles( + False, False, [["==", um(0.42)]], [["==", um(0.42)]], None, inverted=True + ).output("TV1.a", "Min. and max. TopVia1 width = 0.42") + TopVia1_or_Vmim.ext_space(um(0.42)).output("TV1.b", "Min. TopVia1 space = 0.42") + TopMetal1_Nslit.ext_width(um(1.64)).output("TM1.a", "Min. TopMetal1 width = 1.64") + TopMetal1_Nslit.ext_space(um(1.64)).output( + "TM1.b", "Min. TopMetal1 space or notch = 1.64" + ) + + if DENSITY_RULES: + TM1_density.ext_without_density((0.25, 0.7), "ll").output( + "TM1.c/d", "Global TopMetal1 density [%] = 25.00 .. 70.00" + ) + + TopVia2_edgC1_out.ext_rectangles( + False, False, [["==", um(0.9)]], [["==", um(0.9)]], None, inverted=True + ).output("TV2.a", "Min. and max. TopVia2 width = 0.90") + TopVia2.ext_space(um(1.06)).output("TV2.b", "Min. TopVia2 space = 1.06") + TopMetal2_Nslit.ext_width(um(2.0)).output("TM2.a", "Min. TopMetal2 width = 2.00") + TopMetal2_Nslit.ext_space(um(2.0)).output( + "TM2.b", "Min. TopMetal2 space or notch = 2.00" + ) + + if DENSITY_RULES: + TM2_density.ext_without_density((0.25, 0.7), "ll").output( + "TM2.c/d", "Global TopMetal2 density [%] = 25.00 .. 70.00" + ) + + Passiv.ext_width(um(2.1)).output("Pas.a", "Min. Passiv width = 2.10") + Passiv.ext_space(um(3.5)).output("Pas.b", "Min. Passiv space or notch = 3.50") + Activ_pin.ext_not(Activ).output("Pin.a", "Min. Activ enclosure of Activ:pin = 0.00") + GatPoly_pin.ext_not(GatPoly).output( + "Pin.b", "Min. GatPoly enclosure of GatPoly:pin = 0.00" + ) + Metal1_pin.ext_not(Metal1_Nslit).output( + "Pin.e", "Min. Metal1 enclosure of Metal1:pin = 0.00" + ) + Metal2_pin.ext_not(Metal2_Nslit).output( + "Pin.f.M2", "Min. Metal2 enclosure of Metal2:pin = 0.00" + ) + Metal3_pin.ext_not(Metal3_Nslit).output( + "Pin.f.M3", "Min. Metal3 enclosure of Metal3:pin = 0.00" + ) + Metal4_pin.ext_not(Metal4_Nslit).output( + "Pin.f.M4", "Min. Metal4 enclosure of Metal4:pin = 0.00" + ) + Metal5_pin.ext_not(Metal5_Nslit).output( + "Pin.f.M5", "Min. Metal5 enclosure of Metal5:pin = 0.00" + ) + TopMetal1_pin.ext_not(TopMetal1_Nslit).output( + "Pin.g", "Min. TopMetal1 enclosure of TopMetal1:pin = 0.00" + ) + TopMetal2_pin.ext_not(TopMetal2_Nslit).output( + "Pin.h", "Min. TopMetal2 enclosure of TopMetal2:pin = 0.00" + ) + LBE.ext_width(um(100.0)).output("LBE.a", "Min. LBE width = 100.00") + LBE.sized(-um(1500.0) / 2.0, acute_limit).sized( + um(1500.0) / 2.0, acute_limit + ).output("LBE.b", "Max. LBE width = 1500.00") + LBE.ext_with_area([[">", 250000.0]]).output( + "LBE.b1", "Max. LBE area (µm²) = 250000.00" + ) + LBE.ext_space( + um(100.0), consider_intersecting_edges=False, ignore_non_axis_aligned_edges=True + ).output("LBE.c", "Min. LBE space or notch = 100.00") + lbe_in_seal = LBE.inside(EdgeSeal.holes().merge()) + lbe_in_seal.ext_separation( + EdgeSeal, + um(150.0), + consider_intersecting_edges=False, + ignore_non_axis_aligned_edges=True, + ).output("LBE.d", "Min. LBE space to inner edge of EdgeSeal = 150.00") + LBE.with_holes().dup().output("LBE.h", "No LBE ring allowed") + + if DENSITY_RULES: + LBE.ext_with_density((0.2, 1.0), "ll").output( + "LBE.i", "Max. global LBE density [%] = 20.00" + ) + + BiWind.dup().output( + "forbidden.BiWind", "Forbidden drawn layer BiWind on GDS layer 3/0 = 3/0" + ) + PEmWind.dup().output( + "forbidden.PEmWind", "Forbidden drawn layer PEmWind on GDS layer 11/0 = 11/0" + ) + BasPoly.dup().output( + "forbidden.BasPoly", "Forbidden drawn layer BasPoly on GDS layer 13/0 = 13/0" + ) + DeepCo.dup().output( + "forbidden.DeepCo", "Forbidden drawn layer DeepCo on GDS layer 35/0 = 35/0" + ) + PEmPoly.dup().output( + "forbidden.PEmPoly", "Forbidden drawn layer PEmPoly on GDS layer 53/0 = 53/0" + ) + EmPoly.dup().output( + "forbidden.EmPoly", "Forbidden gen./drawn layer EmPoly on GDS layer 53/0 = 53/0" + ) + LDMOS.dup().output( + "forbidden.LDMOS", "Forbidden drawn layer LDMOS on GDS layer 57/0 = 57/0" + ) + PBiWind.dup().output( + "forbidden.PBiWind", "Forbidden drawn layer PBiWind on GDS layer 58/0 = 58/0" + ) + Flash.dup().output( + "forbidden.Flash", "Forbidden drawn layer Flash on GDS layer 71/0 = 71/0" + ) + ColWind.dup().output( + "forbidden.ColWind", "Forbidden drawn layer ColWind on GDS layer 139/0 = 139/0" + ) diff --git a/flow/test/test_klayout_sh.py b/flow/test/test_klayout_sh.py index f7be6fbec9..7e31a86bfa 100644 --- a/flow/test/test_klayout_sh.py +++ b/flow/test/test_klayout_sh.py @@ -49,7 +49,7 @@ def test_ruby_deck_still_needs_klayout(self): "-rd", "report_file=6_drc.lyrdb", "-r", - "/flow/platforms/ihp-sg13g2/drc/sg13g2_minimal.lydrc", + "/flow/platforms/ihp-sg13g2/drc/sg13g2_maximal.lydrc", ) self.assertEqual(result.returncode, 1) self.assertIn("Error: KLayout not found", result.stdout) diff --git a/flow/util/klayout_drc.py b/flow/util/klayout_drc.py index 77635eb8f8..16843e8df3 100644 --- a/flow/util/klayout_drc.py +++ b/flow/util/klayout_drc.py @@ -75,6 +75,38 @@ def projection_limits(lo, hi=None): transparent = _Option("shielded", False) shielded = _Option("shielded", True) +ortho = _Option("edge_kind", "OrthoEdges") +diagonal = _Option("edge_kind", "DiagonalEdges") +diagonal_only = _Option("edge_kind", "DiagonalOnlyEdges") +diamond_limit = _Option("size_mode", 0) +octagon_limit = _Option("size_mode", 1) +square_limit = _Option("size_mode", 2) +acute_limit = _Option("size_mode", 3) +no_limit = _Option("size_mode", 4) +padding_zero = _Option("padding", "zero") +padding_ignore = _Option("padding", "ignore") + + +def tile_size(deck, x, y=None): + """The DRC language's tile_size, in microns.""" + return _Option("tile_size", (deck.v(x) * deck.dbu, deck.v(y or x) * deck.dbu)) + + +def tile_step(deck, x, y=None): + return _Option("tile_step", (deck.v(x) * deck.dbu, deck.v(y or x) * deck.dbu)) + + +def tile_origin(x, y): + """In microns, as DRCTileOrigin.""" + return _Option("tile_origin", (x, y)) + + +def tile_count(x, y): + return _Option("tile_count", (x, y)) + + +def tile_boundary(layer): + return _Option("tile_boundary", layer) class _Universal: @@ -225,7 +257,10 @@ def input(self, layer, datatype=0): def polygons(self, layer, datatype=0): return Source(self, []).polygons(layer, datatype) - def _input(self, layer, datatype, flags, sel): + def labels(self, layer, datatype=0): + return Source(self, []).labels(layer, datatype) + + def _input(self, layer, datatype, flags, sel, cls=None): if isinstance(layer, str): # A layer map expression, "67/0-4,6-43,45-*" lm = db.LayerMap() @@ -238,8 +273,9 @@ def _input(self, layer, datatype, flags, sel): else: li = self.layout.find_layer(layer, datatype) layers = [] if li is None else [li] + cls = cls or db.Region if not layers and self.dss is None: - return Layer(self, db.Region()) + return Layer(self, cls()) it = db.RecursiveShapeIterator(self.layout, self.top, layers) it.shape_flags = flags for c in sel: @@ -256,9 +292,9 @@ def _input(self, layer, datatype, flags, sel): else: it.select_cells(c) if self.dss is not None: - r = db.Region(it, self.dss, db.ICplxTrans(1.0)) + r = cls(it, self.dss, db.ICplxTrans(1.0)) else: - r = db.Region(it, db.ICplxTrans(1.0)) + r = cls(it, db.ICplxTrans(1.0)) return Layer(self, r) def polygon_layer(self): @@ -390,6 +426,10 @@ def polygons(self, layer, datatype=0): ) return self.deck._input(layer, datatype, flags, self.sel) + def labels(self, layer, datatype=0): + """The texts of a layer.""" + return self.deck._input(layer, datatype, db.Shapes.STexts, self.sel, db.Texts) + def _is_region(d): return isinstance(d, db.Region) @@ -603,9 +643,12 @@ def without_angle(self, *args): def _angle(self, inv, args): f = "with_angle" - if args and args[-1] is both: + if both in args: f = "with_angle_both" - args = args[:-1] + args = tuple(a for a in args if a is not both) + if len(args) == 1 and isinstance(args[0], _Option): + kind = getattr(db.Edges, args[0].value) + return self._new(getattr(self.data, f)(kind, inv)) return self._new(getattr(self.data, f)(*_range(None, args, float), inv)) def with_holes(self, *args): @@ -677,9 +720,147 @@ def corners(self, amin=-180.0, amax=None): self.data.corners(float(amin), float(amax), 1, True, True, False, False) ) - def sized(self, dx, dy=None, mode=2): - dy = dx if dy is None else dy - return self._new(self.data.sized(self.deck.v(dx), self.deck.v(dy), mode)) + def sized(self, *args): + """sized(d), sized(dx, dy), each with a sizing mode option.""" + mode = 2 + values = [] + for a in args: + if isinstance(a, _Option) and a.kind == "size_mode": + mode = a.value + else: + values.append(self.deck.v(a)) + return self._new(self.data.sized(values[0], values[-1], mode)) + + # Merged semantics and copies + + def raw(self): + self.data.merged_semantics = False + return self + + def clean(self): + self.data.merged_semantics = True + return self + + def merge(self, *args): + """Merges in place.""" + self.data.merge(*[self.deck.v(a) for a in args]) + return self + + def dup(self): + return self._new(self.data.dup()) + + def is_merged(self): + return self.data.is_merged() + + def hier_count(self): + return self.data.hier_count() + + def bbox(self): + """The bounding box in microns.""" + return db.DBox.from_ibox(self.data.bbox()) * self.deck.dbu + + def select_inside(self, other): + """Selects in place.""" + self.data.select_inside(other.data) + return self + + def texts(self, pattern="*"): + if isinstance(self.data, db.Texts): + return self._new(self.data.with_match(pattern, False)) + return self._new(self.data.texts(pattern, True)) + + def with_distance(self, *args): + return self._new( + self.data.with_distance(*_range(self.deck, args, self.deck.v), False) + ) + + def without_distance(self, *args): + return self._new( + self.data.with_distance(*_range(self.deck, args, self.deck.v), True) + ) + + def with_density(self, *args): + return self._with_density(False, args) + + def without_density(self, *args): + return self._with_density(True, args) + + def _with_density(self, inverse, args): + """Tiles whose density is in a range, as the DRC language's + _with_density: a TilingProcessor with its expression script.""" + limits = [None, None] + size = step = origin = count = boundary = None + padding = "zero" + for a in args: + if isinstance(a, tuple): + limits = list(a) + elif a.kind == "tile_size": + size = a.value + elif a.kind == "tile_step": + step = a.value + elif a.kind == "tile_origin": + origin = a.value + elif a.kind == "tile_count": + count = a.value + elif a.kind == "tile_boundary": + boundary = a.value + elif a.kind == "padding": + padding = a.value + else: + raise ValueError("unexpected density option %r" % (a,)) + if size is None: + raise ValueError("at least the tile_size option needs to be present") + step = step or size + tp = db.TilingProcessor() + tp.dbu = self.deck.dbu + tp.scale_to_dbu = False + tp.tile_size(*step) + if size != step: + xb = 0.5 * (size[0] - step[0]) + yb = 0.5 * (size[1] - step[1]) + tp.tile_border(xb, yb) + tp.var("xoverlap", xb / tp.dbu) + tp.var("yoverlap", yb / tp.dbu) + else: + tp.var("xoverlap", 0) + tp.var("yoverlap", 0) + if origin: + tp.tile_origin(*origin) + if count: + tp.tiles(*count) + res = db.Region() + tp.output("res", res) + tp.input("input", self.data) + tp.threads = self.deck.threads + if boundary is not None: + bnd = boundary.data + else: + bnd = db.Region(self.data.bbox()) + tp.input("boundary", bnd) + tp.var("vmin", limits[0] if limits[0] is not None else 0.0) + tp.var("vmax", limits[1] if limits[1] is not None else 1.0) + tp.var("inverse", inverse) + if padding == "zero": + tp.queue(""" + _tile && ( + var bx = _tile.bbox.enlarged(xoverlap, yoverlap); + var d = to_f(input.area(bx)) / to_f(bx.area); + ((d > vmin - 1e-10 && d < vmax + 1e-10) != inverse) && _output(res, bx, false) + ) +""") + else: + tp.queue(""" + _tile && ( + var bx = _tile.bbox.enlarged(xoverlap, yoverlap); + var ba = boundary.area(bx); + ba > 0 && ( + var d = to_f(input.area(bx)) / to_f(ba); + ((d > vmin - 1e-10 && d < vmax + 1e-10) != inverse) && _output(res, bx, false) + ) + ) +""") + tp.execute("Tiled density") + return self._new(res) # Queries From f5469b0704572097881f5a88a08950613c1daa6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 11:42:49 +0100 Subject: [PATCH 07/11] ihp-sg13g2: LVS deck in Python MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, ihp-sg13g2 had IHP's Ruby LVS runset (sg13g2.lvs, 39 rule_decks/ files and its driver run_lvs.py), and make lvs did not run it. Now make lvs runs sg13g2_lvs.py on the klayout Python module, and gcd matches. What was broken: the runset never ran in ORFS. - config.mk had KLAYOUT_LVS_FILE commented out, naming $(PLATFORM).lylvs, a file that does not exist. - The runset reads -rd input=, schematic=, topcell= and report=; make lvs passes in_gds=, cdl_file=, top_cell= and report_file=, so run as make would, it stops at once: "'source': No layout loaded". - Given its own variables, it runs, and reports no match on gcd: the CDL from cdl.tcl leaves out the physical-only cells, and sg13g2_decap_4/8 have transistors, so 1231 decap instances in the layout have no schematic counterpart. cdl.tcl now writes the CDL with write_cdl -include_fillers. sky130hd's LVS still matches with it. sg13g2_lvs.py is the runset translated line by line, in include order: the layer definitions and derivations on klayout_drc.Layer, the connections, and the device extraction; the runset's Ruby classes as the klayout.db classes of the same names: the SPICE reader delegate (its error fallbacks through super()), the device classes, the device combiners, and the GenericDeviceExtractor subclasses for resistors, varactors, inductors and MIM capacitors. The switches are run_lvs.py's defaults (deep mode, simplify, series and parallel resistors combined). Kept as the Ruby does it: a Metrics::Projection passed as the second positional argument of space_check and notch_check is whole_edges, and the reader deletes [, ], $ and / from element lines. The extracted netlist (target_netlist and its SPICE writer) is not written; the .lvsdb holds it, as for sky130hd. klayout_drc.py gets extent(cell_filter), middle, with_bbox_max and connect for text layers; klayout_lvs.py gets bjt3, bjt4, diode and resistor extractors with a device class, a schematic reader, max_res and min_caps. Checked against the Ruby runset run by the KLayout 0.30.0 application on the same ihp-sg13g2/gcd GDS and CDL (with the decaps), Python on klayout 0.30.12 and 0.30.13, comparing the cross references in the .lvsdb files: clean GDS: match, all: 36 circuits, 232 devices, 837 nets, 239 pins and 1764 subcircuits matched shorted GDS: no match, all (a Metal3 bar between the pins req_msg[12] and req_msg[9]): gcd, 2 nets and 1 subcircuit mismatched, the layout net named "req_msg[12],req_msg[9]"; the rest matched, the same counts in all CDL without the decaps: no match, all, 1231 subcircuits and VDD and VSS mismatched runtime: 7.3 s Ruby, 3.9 s Python The extracted netlists are the same in the Python deck run in the 0.30.0 application and the Ruby runset, but for the pin order of two circuits and the numbers of unnamed nets. Between 0.30.0, 0.30.12 and 0.30.13 the split of the source/drain areas (AS, AD) of combined multi-finger transistors differs; LVS does not compare them. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018PFLaCzFtFbnWE4nPTDwpC Signed-off-by: Øyvind Harboe --- flow/platforms/ihp-sg13g2/config.mk | 2 +- .../lvs/rule_decks/bjt_connections.lvs | 69 - .../lvs/rule_decks/bjt_derivations.lvs | 125 - .../lvs/rule_decks/bjt_extraction.lvs | 81 - .../lvs/rule_decks/cap_connections.lvs | 39 - .../lvs/rule_decks/cap_derivations.lvs | 64 - .../lvs/rule_decks/cap_extraction.lvs | 55 - .../lvs/rule_decks/custom_classes.lvs | 55 - .../lvs/rule_decks/custom_combiner.lvs | 199 - .../lvs/rule_decks/custom_devices.lvs | 225 - .../lvs/rule_decks/custom_extractor.lvs | 405 -- .../lvs/rule_decks/custom_mim_extractor.lvs | 245 - .../lvs/rule_decks/custom_reader.lvs | 346 -- .../lvs/rule_decks/custom_writer.lvs | 66 - .../lvs/rule_decks/devices_connections.lvs | 84 - .../lvs/rule_decks/diode_connections.lvs | 39 - .../lvs/rule_decks/diode_derivations.lvs | 64 - .../lvs/rule_decks/diode_extraction.lvs | 41 - .../lvs/rule_decks/esd_connections.lvs | 108 - .../lvs/rule_decks/esd_derivations.lvs | 147 - .../lvs/rule_decks/esd_extraction.lvs | 116 - .../lvs/rule_decks/general_connections.lvs | 64 - .../lvs/rule_decks/general_derivations.lvs | 104 - .../ihp-sg13g2/lvs/rule_decks/globals.lvs | 68 - .../lvs/rule_decks/ind_connections.lvs | 37 - .../lvs/rule_decks/ind_derivations.lvs | 66 - .../lvs/rule_decks/ind_extraction.lvs | 44 - .../lvs/rule_decks/layers_definitions.lvs | 1856 ------- .../lvs/rule_decks/mos_connections.lvs | 24 - .../lvs/rule_decks/mos_derivations.lvs | 83 - .../lvs/rule_decks/mos_extraction.lvs | 77 - .../lvs/rule_decks/res_connections.lvs | 36 - .../lvs/rule_decks/res_derivations.lvs | 75 - .../lvs/rule_decks/res_extraction.lvs | 89 - .../lvs/rule_decks/rfmos_connections.lvs | 24 - .../lvs/rule_decks/rfmos_derivations.lvs | 47 - .../lvs/rule_decks/rfmos_extraction.lvs | 80 - .../lvs/rule_decks/tap_connections.lvs | 32 - .../lvs/rule_decks/tap_derivations.lvs | 42 - .../lvs/rule_decks/tap_extraction.lvs | 32 - flow/platforms/ihp-sg13g2/lvs/run_lvs.py | 376 -- flow/platforms/ihp-sg13g2/lvs/sg13g2.lvs | 396 -- flow/platforms/ihp-sg13g2/lvs/sg13g2_lvs.py | 4644 +++++++++++++++++ flow/scripts/cdl.tcl | 2 +- flow/util/klayout_drc.py | 40 +- flow/util/klayout_lvs.py | 73 +- 46 files changed, 4753 insertions(+), 6233 deletions(-) delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_extraction.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_extraction.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_classes.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_combiner.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_devices.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_extractor.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_mim_extractor.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_reader.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_writer.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/devices_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_extraction.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_extraction.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/general_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/general_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/globals.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_extraction.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/layers_definitions.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_extraction.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/res_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/res_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/res_extraction.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_extraction.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_connections.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_derivations.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_extraction.lvs delete mode 100644 flow/platforms/ihp-sg13g2/lvs/run_lvs.py delete mode 100644 flow/platforms/ihp-sg13g2/lvs/sg13g2.lvs create mode 100644 flow/platforms/ihp-sg13g2/lvs/sg13g2_lvs.py diff --git a/flow/platforms/ihp-sg13g2/config.mk b/flow/platforms/ihp-sg13g2/config.mk index e6b64e26a8..ad6ba3b76a 100644 --- a/flow/platforms/ihp-sg13g2/config.mk +++ b/flow/platforms/ihp-sg13g2/config.mk @@ -166,7 +166,7 @@ export KLAYOUT_DRC_FILE ?= $(PLATFORM_DIR)/drc/sg13g2_minimal_drc.py #LVS Check export CDL_FILE ?= $(PLATFORM_DIR)/cdl/sg13g2_stdcell.cdl -#export KLAYOUT_LVS_FILE = $(PLATFORM_DIR)/lvs/$(PLATFORM).lylvs +export KLAYOUT_LVS_FILE ?= $(PLATFORM_DIR)/lvs/sg13g2_lvs.py export REMOVE_CELLS_FOR_LEC ?= "bondpad_70* sg13g2*" diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_connections.lvs deleted file mode 100644 index a1cc561104..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_connections.lvs +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ------ BJT CONNECTIONS -------- -#================================ - -logger.info('Starting LVS BJT CONNECTIONS') - -# ============= -# ---- NPN ---- -# ============= - -# General -connect(npn_sub, pwell) - -# npn13G2 nodes connections -connect(npn13G2_te, npn13G2_e_pin) -connect(npn13G2_tc, npn13G2_c_pin) -connect(npn13G2_tb, npn13G2_b_pin) -connect(npn13G2_e_pin, cont_drw) -connect(npn13G2_c_pin, cont_drw) -connect(npn13G2_b_pin, cont_drw) -connect(npn_sub, pwell) -connect(npn13G2_e_pin, emwind_drw) -connect(emwind_drw, metal1_con) - -# npn13G2L nodes connections -connect(npn13G2l_te, npn13G2l_e_pin) -connect(npn13G2l_tc, npn13G2l_c_pin) -connect(npn13G2l_tb, npn13G2l_b_pin) -connect(npn13G2l_e_pin, cont_drw) -connect(npn13G2l_c_pin, cont_drw) -connect(npn13G2l_b_pin, cont_drw) -connect(npn_sub, pwell) - -# npn13G2V nodes connections -connect(npn13G2v_te, npn13G2v_e_pin) -connect(npn13G2v_tc, npn13G2v_c_pin) -connect(npn13G2v_tb, npn13G2v_b_pin) -connect(npn13G2v_e_pin, cont_drw) -connect(npn13G2v_c_pin, cont_drw) -connect(npn13G2v_b_pin, cont_drw) -connect(npn_sub, pwell) - -# ============= -# ---- PNP ---- -# ============= - -# pnp_mpa nodes connections -connect(pnp_mpa_e, cont_drw) -connect(pnp_mpa_b, cont_drw) -connect(pnp_mpa_c, cont_drw) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_derivations.lvs deleted file mode 100644 index 0d56b8999a..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_derivations.lvs +++ /dev/null @@ -1,125 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ------ BJT DERIVATIONS -------- -#================================ - -logger.info('Starting BJT DERIVATIONS') - -# ============= -# ---- NPN ---- -# ============= - -logger.info('Starting NPN-BJT DERIVATIONS') - -bjt_exclude = gatpoly.join(pwell_block).join(nsd_drw) - .join(salblock_drw).join(polyres_drw).join(extblock_drw) - .join(res_drw).join(recog_diode).join(recog_esd) - .join(ind_drw).join(ind_pin).join(substrate_drw) - -npn_exclude = nwell_drw.join(psd_drw).join(nbulay_drw).join(bjt_exclude) - -# ---------- General NPN ---------- -npn_mk = trans_drw.and(pwell).and(ptap_holes) -npn_c_exc = emwind_drw.join(emwihv_drw).join(activ_mask) - .join(nsd_block).join(npn_exclude) -npn_b_exc = emwind_drw.join(emwihv_drw).join(npn_exclude) -npn_sub = npn_mk.not(npn_exclude) -npn_dev = activ.join(activ_mask).and(npn_mk) - -# ---------- npn13G2 ---------- -# npn13G2 exclusion layers -npn13G2_e_exc = activ.join(emwihv_drw).join(npn_exclude) -npn13G2_b_exc = npn_b_exc.join(activ_mask) - -# npn13G2 nodes -npn13G2_e_ = emwind_drw.and(activ_mask).and(nsd_block).and(npn_mk).not(npn13G2_e_exc) -# npn13G2 is a fixed device (0.07um X 0.9um) -npn13G2_e_pin = npn13G2_e_.with_bbox_min(0.07.um).with_bbox_max(0.9.um).with_area(0.063.um) -npn13G2_b_pin = nsd_block.and(npn_mk).not(npn13G2_b_exc) -npn13G2_c_pin = activ.and(npn_mk).not_overlapping(npn_c_exc) - -npn13G2_dev = npn_dev.join(nsd_block).extents.covering(npn13G2_e_pin).covering(npn13G2_b_pin).covering(npn13G2_c_pin) -npn13G2_c = npn13G2_dev.sized(-1.nm) -npn13G2_tc = npn13G2_dev.not(npn13G2_c).interacting(npn13G2_c_pin) -npn13G2_b = npn13G2_dev.not(npn13G2_c_pin) -npn13G2_tb = npn13G2_b.not(npn13G2_e_pin).merged -npn13G2_e = npn13G2_e_pin -npn13G2_te = npn13G2_e - -# ---------- npn13G2L ---------- -# npn13G2L exclusion layers -npn13G2l_e_exc = activ_mask.join(nsd_block).join(emwihv_drw).join(npn_exclude) -npn13G2l_b_exc = npn_b_exc.join(activ).join(nsd_block) - -# npn13G2L nodes -npn13G2l_e_ = emwind_drw.and(activ).and(npn_mk).not(npn13G2l_e_exc) -# npn13G2L has fixed width (0.07um), Length could vary from 1:2.5 um -npn13G2l_e_pin = npn13G2l_e_.with_bbox_min(0.07.um).with_bbox_max(1.um, 2.5.um).with_area(0.07.um, 0.175.um) -npn13G2l_b_pin = activ_mask.and(npn_mk).not(npn13G2l_b_exc) -npn13G2l_c_pin = npn13G2_c_pin - -npn13G2l_dev = npn_dev.covering(npn13G2l_e_pin).covering(npn13G2l_b_pin).covering(npn13G2l_c_pin) -npn13G2l_c = npn13G2l_dev.sized(1.nm) -npn13G2l_tc = npn13G2l_c.not(npn13G2l_dev).interacting(npn13G2l_c_pin) -npn13G2l_b = npn13G2l_dev.not(npn13G2l_c_pin) -npn13G2l_tb = npn13G2l_b.not(npn13G2l_e_pin).merged -npn13G2l_e = npn13G2l_e_pin -npn13G2l_te = npn13G2l_e - -# # ---------- npn13G2V ---------- -# npn13G2V exclusion layers -npn13G2v_e_exc = activ_mask.join(nsd_block).join(emwind_drw).join(npn_exclude) - -# npn13G2V nodes -npn13G2v_e_ = emwihv_drw.and(activ).and(npn_mk).not(npn13G2v_e_exc) -# npn13G2L has fixed width (0.12um), Length could vary from 1:5 um -npn13G2v_e_pin = npn13G2v_e_.with_bbox_min(0.12.um).with_bbox_max(1.um, 5.um).with_area(0.12.um, 0.6.um) -npn13G2v_b_pin = npn13G2l_b_pin -npn13G2v_c_pin = npn13G2l_c_pin - -npn13G2v_dev = npn_dev.covering(npn13G2v_e_pin).covering(npn13G2v_b_pin).covering(npn13G2v_c_pin) -npn13G2v_c = npn13G2v_dev.sized(1.nm) -npn13G2v_tc = npn13G2v_c.not(npn13G2v_dev).interacting(npn13G2v_c_pin) -npn13G2v_b = npn13G2v_dev.not(npn13G2v_c_pin) -npn13G2v_tb = npn13G2v_b.not(npn13G2v_e_pin).merged -npn13G2v_e = npn13G2v_e_pin -npn13G2v_te = npn13G2v_e - -# ============= -# ---- PNP ---- -# ============= - -logger.info('Starting PNP-BJT DERIVATIONS') - -pnp_exclude = trans_drw.join(emwind_drw) - .join(emwihv_drw).join(nsd_block).join(bjt_exclude) - -pnp_mk = ptap_holes.not(pnp_exclude) - -# pnp general nodes DERIVATIONS -pnp_e = pactiv.and(pnp_mk).and(nwell_iso) -pnp_b = nactiv.and(pnp_mk).and(nwell_iso) -pnp_c = ptap.interacting(pnp_mk).not(pnp_exclude) - -# pnp_mpa nodes DERIVATIONS -pnp_mpa_e = pnp_e.and(pnp_b.extents).and(pnp_c.extents) -pnp_mpa_b = pnp_b.interacting(pnp_b.extents.interacting(pnp_mpa_e)) -pnp_mpa_c = pnp_c.interacting(pnp_c.extents.interacting(pnp_mpa_e)) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_extraction.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_extraction.lvs deleted file mode 100644 index d372f5b5c5..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/bjt_extraction.lvs +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ------- BJT EXTRACTION -------- -#================================ - -logger.info('Starting BJT EXTRACTION') - -# ============= -# ---- NPN ---- -# ============= - -logger.info('Starting NPN-BJT EXTRACTION') - -logger.info('Extraction of npn13G2 BJT transistor') -extract_devices(bjt4('npn13G2', CustomBJT4), { - 'C' => npn13G2_c, - 'B' => npn13G2_b, - 'E' => npn13G2_e, - 'S' => npn_sub, - 'tC' => npn13G2_tc, - 'tB' => npn13G2_tb, - 'tE' => npn13G2_te, - 'tS' => npn_sub - }) - -logger.info('Extraction of npn13G2L BJT transistor') -extract_devices(bjt4('npn13G2l', CustomBJT4), { - 'C' => npn13G2l_c, - 'B' => npn13G2l_b, - 'E' => npn13G2l_e, - 'S' => npn_sub, - 'tC' => npn13G2l_tc, - 'tB' => npn13G2l_tb, - 'tE' => npn13G2l_te, - 'tS' => npn_sub - }) - -logger.info('Extraction of npn13G2V BJT transistor') -extract_devices(bjt4('npn13G2v', CustomBJT4), { - 'C' => npn13G2v_c, - 'B' => npn13G2v_b, - 'E' => npn13G2v_e, - 'S' => npn_sub, - 'tC' => npn13G2v_tc, - 'tB' => npn13G2v_tb, - 'tE' => npn13G2v_te, - 'tS' => npn_sub - }) - -# ============= -# ---- PNP ---- -# ============= - -logger.info('Starting PNP-BJT EXTRACTION') - -# pnp_mpa BJT -logger.info('Extracting pnpMPA BJT') -extract_devices(bjt3('pnpMPA', CustomBJT3), { 'C' => pnp_mpa_c.extents, - 'B' => pnp_mpa_b.extents, - 'E' => pnp_mpa_e, - 'tC' => pnp_mpa_c, - 'tB' => pnp_mpa_b, - 'tE' => pnp_mpa_e }) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_connections.lvs deleted file mode 100644 index 645dde2955..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_connections.lvs +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================== -# ------ MIMCAP CONNECTIONS ------- -#================================== - -logger.info('Starting LVS CAP CONNECTIONS') - -# === cap_mim === -connect(cmim_btm, metal5_con) -connect(cmim_top, mim_via) -connect(mim_via, topmetal1_con) - -# === rfcmim === -connect(rfmim_btm, metal5_con) -connect(rfmim_top, mim_via) -connect(rfmim_sub, ptap) - -# === svarivap === -connect(varicap_ports, cont_drw) -connect(varicap_ports, text_drw) -connect(varicap_sub, ptap) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_derivations.lvs deleted file mode 100644 index e8a3194ec8..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_derivations.lvs +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ------- CAP DERIVATIONS ------- -#================================ - -logger.info('Starting CAP DERIVATIONS') - -rfmimcap_exc = ind_drw.join(ind_pin) - -# === MIMCAP === -mimcap_exclude = pwell_block.join(rfmimcap_exc) - -mim_top = mim_drw.overlapping(topmetal1_con).and(metal5_con) -mim_btm = metal5_con.and(mim_drw).sized(0.6.um) -mim_via = vmim_drw.join(topvia1_drw).and(mim_drw) -topvia1_n_cap = topvia1_drw.not(mim_via) - -# === cap_cmim === -cmim_top = mim_top.not(mimcap_exclude) -cmim_btm = mim_btm.covering(cmim_top) -cmim_dev = mim_drw.covering(cmim_top).and(cmim_btm) - -# === rfcmim === -rfmim_area = pwell_block.interacting(mim_drw) -rfmim_top = mim_top.and(rfmim_area).not(rfmimcap_exc) -rfmim_btm = mim_btm.and(rfmim_area).covering(rfmim_top) -rfmim_dev = mim_drw.covering(rfmim_top).and(rfmim_btm) -rfmim_sub = ptap.extents.interacting(rfmim_area) -rfmeas_mk = metal5_con.overlapping(rfmim_btm).and(rfmim_area) - -# === svaricap === -cap_exc = nsd_drw.join(trans_drw).join(emwind_drw) - .join(emwihv_drw).join(salblock_drw).join(polyres_drw) - .join(extblock_drw).join(res_drw).join(activ_mask) - .join(recog_diode).join(recog_esd).join(ind_drw) - .join(ind_pin).join(substrate_drw) - -varicap_exc = pwell.join(pwell_block).join(nwell_holes).join(cap_exc) - -varicap_core = ngate_hv_base.and(nwell_iso).not(varicap_exc) -varicap_diff_port = nactiv.interacting(varicap_core).not(varicap_core) - .and(nwell_iso).not(varicap_exc).sized(-1.nm) -varicap_poly_port = gatpoly.interacting(varicap_core) -varicap_ports = varicap_poly_port.join(varicap_diff_port) -varicap_sub = ptap.and(thickgateox_drw) -varicap_dev_mk = thickgateox_drw.covering(varicap_core).interacting(varicap_ports) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_extraction.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_extraction.lvs deleted file mode 100644 index 00c8d92487..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/cap_extraction.lvs +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================= -# -------- CAP EXTRACTION -------- -#================================= - -logger.info('Starting CAP EXTRACTION') - -# === cap_cmim === -logger.info('Extracting cap_cmim device') -extract_devices(MIMCAPExtractor.new('cap_cmim'), { - 'core' => cmim_top, - 'top_mim' => cmim_top, - 'btm_mim' => cmim_btm, - 'meas_mk' => cmim_dev, - 'dev_mk' => cmim_dev, - }) - -# === rfcmim === -logger.info('Extracting rfcmim device') -extract_devices(MIMCAPExtractor.new('rfcmim'), { - 'core' => rfmim_top, - 'top_mim' => rfmim_top, - 'btm_mim' => rfmim_btm, - 'dev_mk' => rfmim_area.covering(rfmim_dev), - 'meas_mk' => rfmeas_mk, - 'sub_mk' => rfmim_sub, - }) - -# === svaricap === -logger.info('Extracting sg13_hv_svaricap varactor') -extract_devices(GeneralNTerminalExtractor.new('sg13_hv_svaricap', 3), { - 'core' => varicap_core, - 'ports' => varicap_ports, - 'meas_mk' => activ, - 'dev_mk' => varicap_dev_mk, - 'sub_mk' => varicap_sub - }) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_classes.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_classes.lvs deleted file mode 100644 index 078797483b..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_classes.lvs +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -$logger = logger -$unit = dbu - -#================================================ -# --------------- CUSTOM CLASSES ---------------- -#================================================ - -#=============== UTILS =============== -# Method to convert glob pattern to a case-insensitive glob-style pattern -def glob_to_case_insensitive_glob(glob) - wildcards = ['*', '?'] - - pattern = glob.chars.map do |c| - if c =~ /[A-Za-z]/ - "[#{c.upcase}#{c.downcase}]" - elsif wildcards.include?(c) - c # Keep wildcard characters as they are - else - Regexp.escape(c) - end - end.join - - pattern -end - -#=============== CUSTOM READER =================== - -# %include custom_reader.lvs - -#=============== CUSTOM WRITER =================== - -# %include custom_writer.lvs - -#=============== CUSTOM DEVICE =================== - -# %include custom_devices.lvs diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_combiner.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_combiner.lvs deleted file mode 100644 index 8ef59f44e4..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_combiner.lvs +++ /dev/null @@ -1,199 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -$logger = logger -$unit = dbu - -#================================================ -# -------------- CUSTOM COMBINER ---------------- -#================================================ - -# common methods -module DeviceCombinerMethods - private - - # A helper function to check whether two nets are the same - def same_net(a, b, name) - a_net = a.net_for_terminal(name) - b_net = b.net_for_terminal(name) - - # same polarity - same_po = a_net.expanded_name == b_net.expanded_name if a_net && b_net - a_net && b_net && same_po - end - - # A helper function to check whether two device connected in parallel - def supp_parallel(a, b, net1, net2) - a_net1 = a.net_for_terminal(net1) - a_net2 = a.net_for_terminal(net2) - b_net1 = b.net_for_terminal(net1) - b_net2 = b.net_for_terminal(net2) - - return false unless a_net1 && b_net1 && a_net2 && b_net2 - - same_po = a_net1.expanded_name == b_net1.expanded_name && a_net2.expanded_name == b_net2.expanded_name - diff_po = a_net1.expanded_name == b_net2.expanded_name && a_net2.expanded_name == b_net1.expanded_name - - same_po || diff_po - end - - # A helper function to check whether two device connected in series - def supp_series(a, b, net1, net2) - a_net1 = a.net_for_terminal(net1) - a_net2 = a.net_for_terminal(net2) - b_net1 = b.net_for_terminal(net1) - b_net2 = b.net_for_terminal(net2) - - return false unless a_net1 && b_net1 && a_net2 && b_net2 - - series_con_a1 = a_net1.expanded_name == b_net1.expanded_name || a_net1.expanded_name == b_net2.expanded_name - series_con_a2 = a_net2.expanded_name == b_net1.expanded_name || a_net2.expanded_name == b_net2.expanded_name - series_con = series_con_a1 || series_con_a2 - - # Reroute terminal connections based on cluster IDs - if series_con_a1 - if a_net1.expanded_name == b_net1.expanded_name - a.connect_terminal(0, b_net2) - else - a.connect_terminal(0, b_net1) - end - end - - if series_con_a2 - if a_net2.expanded_name == b_net1.expanded_name - a.connect_terminal(1, b_net2) - else - a.connect_terminal(1, b_net1) - end - end - - series_con - end - - # A helper function to check whether two parameters have approximately the same value - def same_parameter(a, b, name) - (a.parameter(name) - b.parameter(name)).abs < 1e-9 - end -end - -class MIMCAPNDeviceCombiner < RBA::GenericDeviceCombiner - include DeviceCombinerMethods - - # Method to check and perform device combination - def combine_devices(a, b) - # Check if both devices have the same net - return false unless same_net(a, b, 'mim_top') && same_net(a, b, 'mim_btm') - - # Check if parameters are the same - return false unless same_parameter(a, b, 'w') && same_parameter(a, b, 'l') - - # Combine by summing up 'm' parameter - a.set_parameter('m', a.parameter('m') + b.parameter('m')) - - # Disconnect the second device and let the system clean it up - b.disconnect_terminal('mim_top') - b.disconnect_terminal('mim_btm') - - # Disconnect mim_sub terminal if present and model name includes 'rfcmim' - b.disconnect_terminal('mim_sub') if b.name.downcase.include?('rfcmim') - - true - end -end - -class DiodeDeviceCombiner < RBA::GenericDeviceCombiner - include DeviceCombinerMethods - - # Method to check and perform device combination - def combine_devices(a, b) - # Check if both devices have the same net - return false unless same_net(a, b, 'A') && same_net(a, b, 'C') - - # Check if parameters are the same - return false unless same_parameter(a, b, 'A') && same_parameter(a, b, 'P') - - # Combine by summing up 'm' parameter - a.set_parameter('m', a.parameter('m') + b.parameter('m')) - - # Disconnect the second device and let the system clean it up - b.disconnect_terminal('A') - b.disconnect_terminal('C') - - true - end -end - -class BJTDeviceCombiner < RBA::GenericDeviceCombiner - include DeviceCombinerMethods - - # Method to check and perform device combination - def combine_devices(a, b) - bjt3_nets = %w[C B E] - bjt4_nets = %w[C B E S] - - # Determine the correct nets based on device type (assuming PNP or NPN) - bjt_nets = a.device_class.name.downcase.include?('pnp') ? bjt3_nets : bjt4_nets - - # Check if terminals have the same net - return false unless bjt_nets.all? { |net| same_net(a, b, net) } - - # Check if parameters are the same - return false unless %w[AE PE].all? { |param| same_parameter(a, b, param) } - - # Combine parameters - a.set_parameter('m', a.parameter('m') + b.parameter('m')) - a.set_parameter('NE', a.parameter('NE') + b.parameter('NE')) - - # Disconnect the second device and let the system clean it up - bjt_nets.each { |term| b.disconnect_terminal(term) } - - true - end -end - -class RESDeviceCombiner < RBA::GenericDeviceCombiner - include DeviceCombinerMethods - - # Method to check and perform device combination - def combine_devices(a, b) - res_nets = [0, 1] # Using id instead of names - - # Check if same parameters - - # Check if terminals series or parallel (With same params) - if supp_parallel(a, b, 0, 1) - return false unless PARALLEL_RES - return false unless %w[w l ps b].all? { |param| same_parameter(a, b, param) } - - a.set_parameter('m', a.parameter('m') + b.parameter('m')) - elsif supp_series(a, b, 0, 1) - return false unless SERIES_RES - return false unless %w[w ps b m].all? { |param| same_parameter(a, b, param) } - - a.set_parameter('l', a.parameter('l') + b.parameter('l')) - else - return false - end - - # Disconnect the second device and let the system clean it up - res_nets.each { |term| b.disconnect_terminal(term) } - - true - end -end diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_devices.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_devices.lvs deleted file mode 100644 index cd812c0230..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_devices.lvs +++ /dev/null @@ -1,225 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -$logger = logger -$unit = dbu - -#================================================ -# --------------- CUSTOM DEVICES ---------------- -#================================================ - -# common methods -module DeviceClassMethods - private - - def add_parameters(*names) - names.each { |name| add_parameter(RBA::DeviceParameterDefinition.new(name)) } - end - - def add_terminals(name, num, sub_en) - (1..num).each do |i| - terminal_name = "#{name}_#{i}" - ter = add_terminal(RBA::DeviceTerminalDefinition.new(terminal_name)) - ter.name = terminal_name - end - return unless sub_en == 1 - - terminal_name = "#{name}_sub" - ter = add_terminal(RBA::DeviceTerminalDefinition.new(terminal_name)) - ter.name = terminal_name - end -end - -# res-custom device calss -class DeviceCustomRes < RBA::DeviceClassResistor - include DeviceClassMethods - - def initialize(name, num) - # clear terminals and parameters of resistor class - clear_parameters - clear_terminals - - add_parameters('w', 'l', 'ps', 'b') - add_parameter(RBA::DeviceParameterDefinition.new('m', 'multiplier', 1, true)) - - if SERIES_RES || PARALLEL_RES - self.combiner = RESDeviceCombiner.new - else - self.combiner =nil - end - - add_terminals(name, num, 0) - end -end - -# BJT-3term device calss -class CustomBJT3 < RBA::DeviceClassBJT3Transistor - - def initialize - super - add_parameter(RBA::DeviceParameterDefinition.new('m', 'multiplier', 1, true)) - self.combiner = BJTDeviceCombiner.new - - enable_parameter('AE', true) - enable_parameter('PE', true) - enable_parameter('NE', true) - end -end - -# BJT-4term device calss -class CustomBJT4 < RBA::DeviceClassBJT4Transistor - include DeviceClassMethods - - def initialize - super - add_parameter(RBA::DeviceParameterDefinition.new('m', 'multiplier', 1, true)) - self.combiner = BJTDeviceCombiner.new - - enable_parameter('AE', true) - enable_parameter('PE', true) - enable_parameter('NE', true) - end -end - -# inductor device calss -class DeviceCustomInd < RBA::DeviceClassInductor - include DeviceClassMethods - - def initialize(name, num) - # clear terminals and parameters of class - clear_parameters - clear_terminals - clear_equivalent_terminal_ids - - add_parameters('w', 's', 'd', 'nr_r') - add_terminals(name, num, 1) - - # 5% tolerance for w,s,d: - equal_ind_parameters = RBA::EqualDeviceParameters::new(parameter_id('w'), 0.0, 0.05) - equal_ind_parameters += RBA::EqualDeviceParameters::new(parameter_id('s'), 0.0, 0.05) - equal_ind_parameters += RBA::EqualDeviceParameters::new(parameter_id('d'), 0.0, 0.05) - # applies the compare delegate: - self.equal_parameters = equal_ind_parameters - - self.combiner = nil - self.supports_serial_combination=false - self.supports_parallel_combination=false - end -end - -# Varactor-custom device calss -class DeviceCustomVaractor < RBA::DeviceClassCapacitorWithBulk - include DeviceClassMethods - - def initialize(name, num) - # clear terminals and parameters of resistor class - clear_parameters - clear_terminals - - add_parameters('w', 'l') - add_terminals(name, num, 1) - end -end - -# res-2term device calss -class RES2 < RBA::DeviceClassResistor - def initialize - super - enable_parameter('W', true) - enable_parameter('L', true) - enable_parameter('R', false) - end -end - -# Diode device class -class EnDiode < RBA::DeviceClassDiode - def initialize - - # 1% tolerance for A,P: - equal_diode_parameters = RBA::EqualDeviceParameters::new(parameter_id('A'), 0.0, 0.01) - equal_diode_parameters += RBA::EqualDeviceParameters::new(parameter_id('P'), 0.0, 0.01) - # applies the compare delegate: - self.equal_parameters = equal_diode_parameters - - # combiner - self.combiner = DiodeDeviceCombiner.new - self.supports_serial_combination=true - self.supports_parallel_combination=true - - add_parameter(RBA::DeviceParameterDefinition.new('m', 'multiplier', 1, true)) - enable_parameter('A', true) - enable_parameter('P', true) - enable_parameter('m', true) - end -end - -# schottky device class -class CustomSchottky < RBA::DeviceClassBJT3Transistor - def initialize - super - clear_parameters - add_parameter(RBA::DeviceParameterDefinition.new('m', 'multiplier', 1, true)) - - self.combiner = SchottckyDeviceCombiner.new - end -end - -# Taps device class -class CustomTap < RBA::DeviceClassDiode - def initialize - super - clear_parameters - add_parameter(RBA::DeviceParameterDefinition.new('A', 'Area', 0, true)) - add_parameter(RBA::DeviceParameterDefinition.new('P', 'Perimeter', 0, true)) - enable_parameter('A', true) - enable_parameter('P', true) - end -end - -# ESD-3term device class -class Esd3Term < RBA::DeviceClassBJT3Transistor - def initialize - super - clear_parameters - add_parameter(RBA::DeviceParameterDefinition.new('m', 'area', 1, true)) - enable_parameter('m', true) - end -end - -# ESD-2term device class -class Esd2Term < RBA::DeviceClassDiode - def initialize - super - clear_parameters - add_parameter(RBA::DeviceParameterDefinition.new('m', 'area', 1, true)) - enable_parameter('m', true) - end -end - -#=========== CUSTOM COMBINER =========== - -# %include custom_combiner.lvs - -#=========== CUSTOM EXTRACTOR =========== - -# %include custom_extractor.lvs - -#========= CUSTOM MIM-EXTRACTOR ========= - -# %include custom_mim_extractor.lvs diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_extractor.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_extractor.lvs deleted file mode 100644 index 55a7a4e8e8..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_extractor.lvs +++ /dev/null @@ -1,405 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -$logger = logger -$unit = dbu - -#================================================ -# --------------- CUSTOM DEVICES ---------------- -#================================================ - -# === GeneralNTerminalExtractor === -class GeneralNTerminalExtractor < RBA::GenericDeviceExtractor - # Extraction of N terminal devices - General Class - - def initialize(name, num) - # Initialize the extractor with a device name and number of terminals. - # - # Args: - # name (String): The name of the device. - # num (Integer): Number of terminals. - create - self.name = name - @num = num - @name = name - end - - def setup - # Set up layers and register device class for extraction. - define_layers - # Register device class for extraction. - if RES_DEV.any? { |res| name.downcase.start_with?(res) } - @reg_dev = DeviceCustomRes.new(name, @num) - elsif name.downcase.include?('varicap') - @reg_dev = DeviceCustomVaractor.new(name, @num) - elsif name.downcase.start_with?('ind') - @reg_dev = DeviceCustomInd.new(name, @num) - else - raise ArgumentError, "Custom-Class for #{name} device is not supported yet, please recheck" - end - register_device_class(@reg_dev) - end - - def get_connectivity(_layout, layers) - # Establish connectivity between layers. - # - # Args: - # _layout: Layout object (unused). - # layers (Array): Array of layer objects. - # - # Returns: - # Connectivity object representing the connections between layers. - dev = layers[0] - ports = layers[1] - meas_mk = layers[2] - dev_mk = layers[3] - - conn = RBA::Connectivity.new - conn.connect(dev, dev) - conn.connect(dev, dev_mk) - conn.connect(dev, meas_mk) - conn.connect(ports, dev_mk) - conn.connect(meas_mk, dev_mk) - - # Sub connection for some devices - if name.downcase.start_with?('ind') || name.downcase.include?('varicap') - sub_mk = layers[4] - conn.connect(sub_mk, dev_mk) - end - - conn - end - - def extract_devices(layer_geometry) - # Extract devices based on layer geometry. - # - # Args: - # layer_geometry (Array): Array of layer geometries. - dev, ports, meas_mk, dev_mk, sub_mk = layer_geometry - - dev_mk.merged.each do |region| - if ports.size != @num - $logger.info("#{@name} device terminals (#{@num}) don't touch device marker correctly") - $logger.info("No. of ports exist for #{@name} is #{ports.size}, should be #{@num}") - else - device = create_device - set_device_parameters(device, region, dev, ports, meas_mk, dev_mk) - define_and_sort_terminals(device, ports, sub_mk) - end - end - end - - private - - def define_layers - # Define layers for extraction. - define_layer('core', 'core Layer') - define_layer('ports', 'Connect Terminal') - define_layer('meas_mk', 'Measuring parameters marker') - define_layer('dev_mk', 'Device Marker') - # Define sub layer for some devices - if name.downcase.start_with?('ind') || name.downcase.include?('varicap') - define_layer('sub_mk', 'Substrate Marker') - end - end - - def set_device_parameters(device, region, dev, ports, meas_mk, dev_mk) - # Set device parameters based on device type. - # - # Args: - # device: Device object to set parameters for. - # region: Region representing the measured region. - # dev: Device layer object. - # ports: ports layer object. - # meas_mk: Measuring marker layer object. - # dev_mk: main marker layer object. - # - # Returns: - # None - - if RES_DEV.any? { |res| name.downcase.start_with?(res) } - width, length, poly_sp, bends = calc_res_params(dev, ports, meas_mk) - device.set_parameter('w', width * $unit) - device.set_parameter('l', length * $unit) - device.set_parameter('ps', poly_sp * $unit) - device.set_parameter('b', bends) - - elsif name.downcase.include?('varicap') - width, length = calc_varicap_params(dev, ports, meas_mk, dev_mk) - device.set_parameter('w', width * $unit) - device.set_parameter('l', length * $unit) - - elsif name.downcase.start_with?('ind') - width, space, diameter, no_turns = calc_ind_params(dev, ports, meas_mk, dev_mk, region) - device.set_parameter('w', width * $unit) - device.set_parameter('s', space * $unit) - device.set_parameter('d', diameter * $unit) - device.set_parameter('nr_r', no_turns) - end - end - - def calc_res_params(dev, ports, meas_mk) - # Width - width_edges = dev.edges.and(ports.edges) - width = get_uniq_length(width_edges) - - # Length - length_edges = dev.edges.interacting(width_edges).not(width_edges) - length, _ = get_min_max_length(length_edges) - - # Bends - corners = meas_mk.interacting(dev).corners.not_interacting(ports).count - bends = corners / 4 - - # poly_space between bends - if bends.positive? - poly_sp_polygon = meas_mk.interacting(dev) - poly_sp = get_notch_min(poly_sp_polygon, 10 * length) - length = length + width - end - - # Default values - width ||= 0 - length ||= 0 - poly_sp ||= 0 - bends ||= 0 - - [width, length, poly_sp, bends] - end - - def calc_varicap_params(dev, _ports, meas_mk, _dev_mk) - # Width & Length - width_edges = dev.edges.not_interacting(meas_mk.edges) - length, width = get_min_max_length(width_edges) - - # Default values - width ||= 0 - length ||= 0 - - [width, length] - end - - def calc_ind_params(dev, ports, meas_mk, dev_mk, region) - # Get upper limit for width, space - _, max_mk_len = get_min_max_length(dev_mk.edges) - - # Width - meas_sel = meas_mk.merged & region - width = get_width_val(meas_sel, max_mk_len) - - # space - space = get_space_val(meas_sel, max_mk_len) - - # Turns - # Calc steps used for no. of turns: - # step1: Get count of inductor metal (catch if we have more 1) - # Step2: For more than 1 turns, get number of holes - # Step3: Turns = 1 + (holes - 1)/2 - no_turns_init = meas_mk.merged.count - no_turns = no_turns_init - - ## Old implementation - # if no_turns_init == 1 - # no_turns = no_turns_init - # else - # no_turns_pre1 = dev.merged.holes.count - # no_turns_pre2 = (no_turns_pre1 - 1) / 2 - # no_turns = 1 + no_turns_pre2.ceil - # end - - # Diameter - # Calc steps used for diameter: - # step1: Get extent of the inductor core - # step2: Exclude edges that touch inductor pins - # step3: Get length of the remaining edge (Outer diameter) - # step4: Get internal diameter --> din = dout - ((turns -1) * 2s) - (turns * w) - diam_extents = dev.extents.edges - diam_edge_exc = diam_extents.interacting(ports) - diam_edge = diam_extents.not_interacting(diam_edge_exc) - diameter = diam_edge.length - diameter = diameter - (2 * (no_turns - 1) * space) - (2 * no_turns * width) - diameter = diameter.negative? ? 0 : diameter - - # Default values - width ||= 0 - space ||= 0 - diameter ||= 0 - no_turns ||= 1 - - [width, space, diameter, no_turns] - end - - def define_and_sort_terminals(device, ports, sub_mk) - # Define and sort terminals based on location. - # - # Args: - # device: Device object to define terminals for. - # ports: Contact layer object containing terminals. - # sub_mk: substrate marker layer object. - # - # Returns: - # None - - # If none of the substrings match, sorted_ports remains the result of sort_polygons(ports) - substrings = %w[varicap] - - # Initialize sorted_ports with a default value - sorted_ports = nil - - # Iterate over each substring - substrings.each do |substring| - if name.include?(substring) - sorted_ports = ports - break # Exit loop if a match is found - end - end - - # If none of the substrings match, sorted_ports remains the result of sort_polygons(ports) - sorted_ports ||= sort_polygons(ports) - - # Define sub if exist (should be defined before other terminals) - if name.downcase.start_with?('ind') || name.downcase.include?('varicap') - if sub_mk.is_empty? - $logger.info("Sub terminal for #{@name} device doesn't exist, please recheck") - return nil - else - define_terminal(device, @reg_dev.terminal_id("#{name}_sub"), 4, sub_mk[0]) - end - end - - # Defination main terminals - (1..@num).each do |i| - define_terminal(device, @reg_dev.terminal_id("#{name}_#{i}"), 1, sorted_ports[i - 1]) - end - end - - def sort_polygons(polygons) - # Sort polygons points. - # - # Args: - # polygons: Polygons to sort. - # - # Returns: - # Sorted polygons. - # - # Note: - # This function sorts the points of the input polygons to be ordered as expected. - # It takes an array of polygons and returns the sorted array. - # The sorting is based on the x-coordinate of the first point of each polygon. - con_polygons = [] - - polygons.merged.each do |ports_pl| - con_edges = [] - ports_pl.each_edge do |con_ed| - con_edges.append([con_ed.x1, con_ed.y1]) - con_edges.append([con_ed.x2, con_ed.y2]) - end - con_polygons.append(con_edges.uniq) - end - sorted_ports_polygons = con_polygons.sort_by(&:first) - sorted_ports = [] - sorted_ports_polygons.each do |sorted_pl| - ports_pl = RBA::DPolygon.new([RBA::DPoint.new(sorted_pl[0][0], sorted_pl[0][1]), - RBA::DPoint.new(sorted_pl[1][0], sorted_pl[1][1]), - RBA::DPoint.new(sorted_pl[2][0], sorted_pl[2][1]), - RBA::DPoint.new(sorted_pl[3][0], sorted_pl[3][1])]) - sorted_ports.append(ports_pl) - end - - sorted_ports - end - - def get_uniq_length(sel_edges) - # Extract uniqe length value for some selected edges - lengths = [] - sel_edges.each do |edge| - lengths << edge.length - end - lengths.uniq! - lengths.size == 1 ? lengths[0] : 0.0 - end - - def get_sep_val(sel_edges, sep_edges, sep_val) - # Extract distance between edges for separation check - proj = RBA::Metrics::Projection - sep_paris = sel_edges.separation_check(sep_edges, sep_val, proj) - sep_values = [] - sep_paris.each do |edge| - sep_values << edge.distance - end - sep_values.min - end - - def get_space_val(sel_polygon, sep_val) - # Extract distance between edges for space check - proj = RBA::Metrics::Projection - space_paris = sel_polygon.space_check(sep_val, proj) - space_values = [] - space_paris.each do |edge| - space_values << edge.distance - end - space_values.min - end - - def get_width_val(sel_polygon, width_val) - # intra-polygon spacing check - proj = RBA::Metrics::Projection - width_paris = sel_polygon.width_check(width_val, metrics: proj, min_projection: 10) - width_values = [] - width_paris.each do |edge| - width_values << edge.distance - end - # Group the array elements by their occurrences - width_values = width_values.reject(&:zero?) - - width_values.min - end - - def get_notch_min(sel_polygon, sep_val) - # intra-polygon spacing check - proj = RBA::Metrics::Projection - space_paris = sel_polygon.notch_check(sep_val, proj) - space_values = [] - space_paris.each do |edge| - space_values << edge.distance - end - space_values.min - end - - def get_notch_max(sel_polygon, sep_val) - # intra-polygon spacing check - proj = RBA::Metrics::Projection - space_paris = sel_polygon.notch_check(sep_val, proj) - space_values = [] - space_paris.each do |edge| - space_values << edge.distance - end - space_values.max - end - - def get_min_max_length(sel_edges) - # Extract max length value for some selected edges - lengths = [] - sel_edges.each do |edge| - lengths << edge.length - end - lengths.minmax - end -end - diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_mim_extractor.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_mim_extractor.lvs deleted file mode 100644 index 60d39712ff..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_mim_extractor.lvs +++ /dev/null @@ -1,245 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -$logger = logger -$unit = dbu - -#================================================ -# --------------- CUSTOM DEVICES ---------------- -#================================================ - -# MIM-custom device calss -class DeviceCustomMIM < RBA::DeviceClassCapacitor - def initialize(name) - # clear terminals and parameters of resistor class - clear_parameters - clear_terminals - clear_equivalent_terminal_ids - - # Adding params - add_parameter(RBA::DeviceParameterDefinition.new('w', 'width', 0, false)) - add_parameter(RBA::DeviceParameterDefinition.new('l', 'length', 0, false)) - add_parameter(RBA::DeviceParameterDefinition.new('A', 'area', 0, true)) - add_parameter(RBA::DeviceParameterDefinition.new('P', 'perimeter', 0, true)) - add_parameter(RBA::DeviceParameterDefinition.new('m', 'multiplier', 1, true)) - - # Adding terminals - ter1 = add_terminal(RBA::DeviceTerminalDefinition.new("mim_top")) - ter2 = add_terminal(RBA::DeviceTerminalDefinition.new("mim_btm")) - ter1.name = "mim_top" - ter2.name = "mim_btm" - - # Adding extra param & terminal for rfcmim - return unless name.downcase.include?('rfcmim') - - add_parameter(RBA::DeviceParameterDefinition.new('wfeed', 'feed width', 0, true)) - sub_ter = add_terminal(RBA::DeviceTerminalDefinition.new("mim_sub")) - sub_ter.name = "mim_sub" - end -end - -# === MIMCAPExtractor === -class MIMCAPExtractor < RBA::GenericDeviceExtractor - # Extraction of N terminal devices - General Class - - def initialize(name) - # Initialize the extractor with a device name and number of terminals. - # - # Args: - # name (String): The name of the device. - - self.name = name - @name = name - end - - def setup - # Set up layers and register device class for extraction. - define_layers - # Register device class for extraction. - @reg_dev = DeviceCustomMIM.new(name) - - # Disable combination for rfcmim - if name.downcase.include?('rfcmim') - @reg_dev.combiner = nil - else - @reg_dev.combiner = MIMCAPNDeviceCombiner.new - end - register_device_class(@reg_dev) - end - - def get_connectivity(_layout, layers) - # Establish connectivity between layers. - # - # Args: - # _layout: Layout object (unused). - # layers (Array): Array of layer objects. - # - # Returns: - # Connectivity object representing the connections between layers. - dev = layers[0] - top_mim = layers[1] - btm_mim = layers[2] - dev_mk = layers[3] - meas_mk = layers[4] - - conn = RBA::Connectivity.new - conn.connect(dev, dev) - conn.connect(dev, dev_mk) - conn.connect(dev, meas_mk) - conn.connect(top_mim, dev_mk) - conn.connect(btm_mim, dev_mk) - - # Sub connection for rfcmim - if name.downcase.include?('rfcmim') - sub_mk = layers[5] - conn.connect(sub_mk, dev_mk) - end - - conn - end - - def extract_devices(layer_geometry) - # Extract devices based on layer geometry. - # - # Args: - # layer_geometry (Array): Array of layer geometries. - dev, top_mim, btm_mim, dev_mk, meas_mk, sub_mk = layer_geometry - - dev_mk.merged.each do |_region| - if top_mim.size != 1 - $logger.info("No. of ports exist for #{@name} topmetal is #{top_mim.size}, should be 1") - elsif btm_mim.size != 1 - $logger.info("No. of ports exist for #{@name} btmmetal is #{btm_mim.size}, should be 1") - else - device = create_device - set_device_parameters(device, dev, dev_mk, meas_mk) - define_terminals(device, top_mim, btm_mim, sub_mk) - end - end - end - - private - - def define_layers - # Define layers for extraction. - define_layer('core', 'core Layer') - define_layer('top_mim', 'Connect Terminal for top mim') - define_layer('btm_mim', 'Connect Terminal for btm mim') - define_layer('dev_mk', 'Device Marker') - define_layer('meas_mk', 'Measuring parameters marker') - - # Define sub layer for some devices - return unless name.downcase.include?('rfcmim') - - define_layer('sub_mk', 'Substrate layer') - end - - def set_device_parameters(device, dev, dev_mk, meas_mk) - # Set device parameters based on device type. - # - # Args: - # device: Device object to set parameters for. - # dev: Device layer object. - # dev_mk: device marker layer object. - # meas_mk: meas marker layer object. - # - # Returns: - # None - - width, length, wfeed = calc_cmim_params(dev, dev_mk, meas_mk) - - if name.downcase.include?('rfcmim') - device.set_parameter('l', width * $unit) - device.set_parameter('w', length * $unit) - device.set_parameter('wfeed', wfeed * $unit) - else - device.set_parameter('w', width * $unit) - device.set_parameter('l', length * $unit) - end - device.set_parameter('A', width * length * $unit * $unit) - device.set_parameter('P', (width + length) * 2 * $unit) - - end - - def calc_cmim_params(dev, dev_mk, meas_mk) - # Width & Length - dev_edges = dev.edges - width_edges = dev_edges.with_angle(0, false) - len_edges = dev_edges.not(width_edges) - width = get_uniq_length(width_edges) - length = get_uniq_length(len_edges) - - # Wfeed - wfeed_edges = meas_mk.edges.and(dev_mk.edges) - wfeed = get_uniq_length(wfeed_edges) - - # Default values - width ||= 0 - length ||= 0 - wfeed ||= 0 - - [width, length, wfeed] - end - - def define_terminals(device, top_mim, btm_mim, sub_mk) - # Define terminals based on location. - # - # Args: - # device: Device object to define terminals for. - # top_mim: Contact layer object containing mim top metal. - # top_mim: Contact layer object containing mim btm metal. - # sub_mk: substrate marker layer object. - # - # Returns: - # None - - # Define sub if exist (should be defined before other terminals) - if name.downcase.include?('rfcmim') - if sub_mk.is_empty? - $logger.info("Sub terminal for #{@name} device doesn't exist, please recheck") - return nil - else - define_terminal(device, @reg_dev.terminal_id("mim_sub"), 5, sub_mk[0]) - end - end - - # Defination main terminals - define_terminal(device, @reg_dev.terminal_id("mim_top"), 1, top_mim[0]) - define_terminal(device, @reg_dev.terminal_id("mim_btm"), 2, btm_mim[0]) - end - - def get_min_max_length(sel_edges) - # Extract max length value for some selected edges - lengths = [] - sel_edges.each do |edge| - lengths << edge.length - end - lengths.minmax - end - - def get_uniq_length(sel_edges) - # Extract uniqe length value for some selected edges - lengths = [] - sel_edges.each do |edge| - lengths << edge.length - end - lengths.uniq! - lengths.size == 1 ? lengths[0] : 0.0 - end -end diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_reader.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_reader.lvs deleted file mode 100644 index 0b517fba59..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_reader.lvs +++ /dev/null @@ -1,346 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#=========== CUSTOM READER =========== - -# Custom reader for subcircuit models -class CustomReader < RBA::NetlistSpiceReaderDelegate - # Cleanup sch for R, C elements - def clean_sch(line, element) - # Extracting parameters with values - valid_params = line.scan(/\b\w+\s*=\s*\S+\b/) - - case element - when 'R' - # For 2 term res [ ] - num_terms = 3 - when 'C' - # Determine number of terms based on component type - num_terms = - if line.downcase.include?('varicap') - 5 - elsif line.downcase.include?('rfcmim') - 4 - else - 3 - end - end - line_no_param = line.split(' ').take(num_terms).join(' ') - "#{line_no_param.strip} #{valid_params.join(' ')}" - end - - # Override parse_element method to handle exceptions gracefully - def parse_element(line, element) - # clean line - line = line.delete('[]$\\/') - - # Prep sch for R, C - line = clean_sch(line, element) if %w[R C].include?(element) - - super - rescue StandardError - case element - when 'C' - if line.downcase.include?('varicap') - super(line.to_s, 'M') - elsif line.downcase.include?('rfcmim') - super(line.to_s, 'Q') - else - super("#{line} C=1e-18", element) - end - when 'R' - super("#{line} R=0", element) - when 'D' - if line.downcase.include?('diodev') || line.downcase.include?('schottky') - super(line.to_s, 'Q') - else - super(line.to_s, element) - end - when 'L' - if line.downcase.include?('inductor3') - super("#{line} L=0", 'M') - else - super("#{line} L=0", element) - end - else - super - end - end - - # Override the element method to handle different types of elements - def element(circuit, ele, name, model, value, nets, params) - if CUSTOM_READER.include?(ele) - process_device(ele, circuit, name, model, nets, params) - else - super - end - true - end - - private - - # Process device element - def process_device(ele, circuit, name, model, nets, params) - cls = circuit.netlist.device_class_by_name(model) - cls ||= create_device_class(ele, circuit, model, nets.size) - - device = circuit.create_device(cls, name) - connect_terminals(ele, device, model, nets) - map_params(ele, device, model, params) - end - - # Create or retrieve the device class based on the element type, model name, and number of nets. - # - # @param ele [String] The type of element (C, R, Q, L). - # @param circuit [Circuit] The circuit object to which the device class will be added. - # @param model [String] The model name of the device class. - # @param num_nets [Integer] The number of nets the device class should have. - # @return [RBA::DeviceClass] The created or retrieved device class. - def create_device_class(ele, circuit, model, num_nets) - cls = case ele - when 'M' then RBA::DeviceClassMOS4Transistor.new - when 'C' then create_capacitor(model, num_nets) - when 'R' then create_resistor(model, num_nets) - when 'Q' then create_bjt(model, num_nets) - when 'L' then DeviceCustomInd.new(model, num_nets - 1) - when 'D' then create_diode(model) - else - return super - end - - cls.name = model - circuit.netlist.add(cls) - cls - end - - # Create a capacitor device class. - def create_capacitor(model, num_nets) - if model.downcase.include?('varicap') - raise ArgumentError, 'Varicap should have 4 nodes, please recheck' unless num_nets == 4 - - DeviceCustomVaractor.new(model, num_nets - 1) - else - raise ArgumentError, 'Capacitor should have 2 or 3 nodes, please recheck' unless [2, 3].include?(num_nets) - - DeviceCustomMIM.new(model) - - end - end - - # Create a diode device class. - def create_diode(model) - if model.downcase.include?('diodev') || model.downcase.include?('schottky') - Esd3Term.new - elsif model.downcase.include?('nmoscl') - Esd2Term.new - else - EnDiode.new - end - end - - # Create a resistor device class. - def create_resistor(model, num_nets) - if RES_DEV.any? { |res| model.downcase.start_with?(res) } - DeviceCustomRes.new(model, num_nets) - elsif num_nets == 2 && model.downcase.include?('tap') - RBA::DeviceClassDiode.new - elsif num_nets == 2 - RBA::DeviceClassResistor.new - elsif num_nets == 3 - RBA::DeviceClassResistorWithBulk.new - else - raise ArgumentError, 'Resistor should have two or three nodes, please recheck' - end - end - - # Create a bjt device class. - def create_bjt(model, _num_nets) - if model.downcase.include?('pnp') - CustomBJT3.new - else - CustomBJT4.new - end - end - - # Connect device terminals based on element type, device, model, and nets. - # - # @param ele [String] The type of element (C, R, Q). - # @param device [RBA::Device] The device object to which terminals will be connected. - # @param model [String] The model name of the device. - # @param nets [Array] Array of net names to which terminals will be connected. - def connect_terminals(ele, device, model, nets) - term_list = terminal_list_for_element(ele, model, nets) - - term_list.each_with_index do |t, index| - device.connect_terminal(t, nets[index]) - end - end - - # Determine terminal list based on element type, model, and nets. - def terminal_list_for_element(ele, model, nets) - case ele - when 'M' - %w[D G S B] - when 'Q' - model.downcase.include?('pnp') ? %w[C B E] : %w[C B E S] - when 'C' - model.downcase.include?('varicap') ? gen_term_with_sub(model, nets.size) : gen_mim_terms(model) - when 'R' - if RES_DEV.any? { |res| model.downcase.start_with?(res) } - gen_term_names(model, nets.size) - elsif model.downcase.include?('tap') - %w[C A] - else - nets.size == 3 ? %w[A B W] : %w[A B] - end - when 'D' - if model.downcase.include?('diodevdd') - %w[B E C] - elsif model.downcase.include?('diodevss') - %w[C E B] - elsif model.downcase.include?('schottky') - %w[E B C] - elsif model.downcase.include?('nmoscl') - %w[C A] - else - %w[A C] - end - when 'L' - gen_term_with_sub(model, nets.size) - else - gen_term_names(model, nets.size) - end - end - - # Generate terminal names based on model and the number of nets. - def gen_term_names(model, size) - (0...size).map { |i| "#{model}_#{i + 1}" } - end - - # Generate terminal names based on model and the number of nets. - def gen_mim_terms(model) - terms = %w[mim_top mim_btm] - - return terms unless model.downcase.include?('rfcmim') - - terms << 'mim_sub' # Add sub terminal - terms - end - - # Generate terminal names based on model and the number of nets. - def gen_term_with_sub(model, size) - terms = (0...size - 1).map { |i| "#{model}_#{i + 1}" } - terms << "#{model}_sub" # Add sub terminal - end - - # Map parameters based on the model type. - # - # @param ele [String] The type of element (M, C, R, Q, L, D). - # @param device [RBA::Device] The device object to which parameters will be mapped. - # @param model [String] The model name of the device. - # @param params [Hash] Hash containing parameter values. - def map_params(ele, device, model, params) - case ele - when 'M' - map_mos_params(device, params) - when 'Q' - map_bjt_params(device, model, params) - when 'C' - map_capacitor_params(device, model, params) - when 'R' - map_resistor_params(device, model, params) - when 'D' - map_diode_params(device, model, params) - when 'L' - map_inductor_params(device, params) - else - raise ArgumentError, "#{ele} device with model #{model} is not supported, please recheck" - end - end - - # Map parameters for mos devices. - def map_mos_params(device, params) - device.set_parameter('W', (params['W'] || 0.0) * (params['M'] || 1.0) * 1e6) - device.set_parameter('L', (params['L'] || 0.0) * 1e6) - end - - # Map parameters for a BJT device. - def map_bjt_params(device, model, params) - if model.downcase.include?('pnp') - device.set_parameter('AE', (params['A'] || ((params['W'] || 0.0) * (params['L'] || 0.0))) * 1e12) - device.set_parameter('PE', (params['P'] || (((params['W'] || 0.0) + (params['L'] || 0.0)) * 2)) * 1e6) - else - device.set_parameter('AE', (params['AE'] || ((params['WE'] || 0.0) * (params['LE'] || 0.0))) * 1e12) - device.set_parameter('PE', (params['PE'] || (((params['WE'] || 0.0) + (params['LE'] || 0.0)) * 2)) * 1e6) - end - device.set_parameter('NE', params['M'] || params['NE'] || 1.0) - device.set_parameter('m', params['M'] || params['NE'] || 1.0) - end - - # Map parameters for a diode device. - def map_diode_params(device, model, params) - unless model.downcase.include?('diodev') || model.downcase.include?('schottky') || model.downcase.include?('nmoscl') - device.set_parameter('A', (params['A'] || ((params['W'] || 0.0) * (params['L'] || 0.0))) * 1e12) - device.set_parameter('P', (params['P'] || (((params['W'] || 0.0) + (params['L'] || 0.0)) * 2)) * 1e6) - end - device.set_parameter('m', params['M'] || 1.0) - end - - # Map parameters for a capacitor device. - def map_capacitor_params(device, model, params) - device.set_parameter('w', (params['W'] || 0.0) * 1e6) - device.set_parameter('l', (params['L'] || 0.0) * 1e6) - device.set_parameter('m', params['M'] || params['MF'] || 1.0) if model.downcase.include?('cap_cmim') - - if model.downcase.include?('mim') - device.set_parameter('A', (params['A'] || ((params['W'] || 0.0) * (params['L'] || 0.0))) * 1e12) - device.set_parameter('P', (params['P'] || (((params['W'] || 0.0) + (params['L'] || 0.0)) * 2)) * 1e6) - end - return unless model.downcase.include?('rfcmim') - - device.set_parameter('wfeed', (params['WFEED'] || 0.0) * 1e6) - end - - # Map parameters for a resistor device. - def map_resistor_params(device, model, params) - if model.downcase.include?('tap') - device.set_parameter('A', (params['A'] || ((params['W'] || 0.0) * (params['L'] || 0.0))) * 1e12) - device.set_parameter('P', - (params['P'] || params['PERIM'] || (((params['W'] || 0.0) + (params['L'] || 0.0)) * 2)) * 1e6) - elsif RES_DEV.any? { |res| model.downcase.start_with?(res) } - device.set_parameter('w', (params['W'] || 0.0) * 1e6) - device.set_parameter('l', (params['L'] || 0.0) * 1e6) - device.set_parameter('ps', (params['PS'] || 0.0) * 1e6) - device.set_parameter('b', params['B'] || 0.0) - device.set_parameter('m', params['M'] || 1.0) - else - device.set_parameter('W', (params['W'] || params['WIDTH'] || 0.0) * (params['M'] || 1.0) * 1e6) - device.set_parameter('L', (params['L'] || params['LENGTH'] || 0.0) * (params['S'] || 1.0) * 1e6) - device.set_parameter('R', (params['R'] || 0.0) * (params['S'] || 1.0) / (params['M'] || 1.0)) - end - end - - # Map parameters for an inductor device. - def map_inductor_params(device, params) - device.set_parameter('w', (params['W'] || 0.0) * 1e6) - device.set_parameter('s', (params['S'] || 0.0) * 1e6) - device.set_parameter('d', (params['D'] || 0.0) * 1e6) - device.set_parameter('nr_r', params['NR_R'] || 0.0) - end -end diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_writer.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_writer.lvs deleted file mode 100644 index 09c33fe677..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/custom_writer.lvs +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#=============== CUSTOM WRITER =================== - -# %include globals.lvs - -# Custom writer for SPICE netlists -class CustomWriter < RBA::NetlistSpiceWriterDelegate - # Write device to SPICE format - # @param device [RBA::Device] The device to be written - def write_device(device) - device_class = device.device_class - str = generate_device_prefix(device, device_class) - str += generate_device_terminals(device, device_class) - str += "#{device_class.name} " - str += generate_default_parameters(device, device_class) - emit_line(str) - end - - private - - # Generate device prefix using the global prefix map - def generate_device_prefix(device, device_class) - prefix = PREFIX_MAP[device_class.name] || device.id.to_s - "#{prefix}#{device.expanded_name} " - end - - # Generate device terminals - def generate_device_terminals(device, device_class) - terminals = device_class.terminal_definitions.map do |td| - net_to_string(device.net_for_terminal(td.id)) - end - "#{terminals.join(' ')} " - end - - # Generate parameters with given keys - def generate_parameters(device, *keys) - parameters = keys.map { |key| "#{key}=#{device.parameter(key)}" } - parameters.join(' ') - end - - # Generate default parameters for the device - def generate_default_parameters(device, device_class) - parameters = device_class.parameter_definitions.map do |pd| - format('%s=%.12g', name: pd.name, value: device.parameter(pd.id)) - end - parameters.join(' ') - end -end diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/devices_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/devices_connections.lvs deleted file mode 100644 index 186f0aeca1..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/devices_connections.lvs +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================================ -#------------ DEVICES CONNECTIVITY -------------- -#================================================ - -logger.info('Starting GF180 LVS connectivity setup') - -#================================ -# ----- GENERAL CONNECTIONS ----- -#================================ - -# %include general_connections.lvs - -#================================ -# ----- MOSFET CONNECTIONS ------ -#================================ - -# %include mos_connections.lvs - -#================================ -# ---- RF-MOSFET CONNECTIONS ---- -#================================ - -# %include rfmos_connections.lvs - -#================================ -# ------ BJT CONNECTIONS -------- -#================================ - -# %include bjt_connections.lvs - -#================================ -# ----- DIODE CONNECTIONS ------- -#================================ - -# %include diode_connections.lvs - -#================================ -# ------- RES CONNECTIONS ------- -#================================ - -# %include res_connections.lvs - -#================================== -# -------- CAP CONNECTIONS -------- -#================================== - -# %include cap_connections.lvs - -#================================ -# ------- ESD CONNECTIONS ------- -#================================ - -# %include esd_connections.lvs - -#================================= -# ----- Inductor CONNECTIONS ----- -#================================= - -# %include ind_connections.lvs - -#================================ -# ------- Taps CONNECTIONS ------ -#================================ - -# %include tap_connections.lvs diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_connections.lvs deleted file mode 100644 index ad39928fe8..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_connections.lvs +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ----- DIODE CONNECTIONS ------- -#================================ - -logger.info('Starting LVS DIODE CONNECTIONS') - -# dantenna diode -connect(dantenna_n, cont_drw) -connect(dantenna_p, pwell) - -# dantenna diode -connect(dpantenna_n, nwell_drw) -connect(dpantenna_p, cont_drw) - -# dantenna diode -connect(schottcky_p_1x1, schottcky_p) -connect(schottcky_p, metal1_con) -connect(schottcky_n, schottcky_n_port) -connect(schottcky_n_port, schottcky_n_con) -connect(schottcky_n_con, metal1_con) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_derivations.lvs deleted file mode 100644 index 3e35fde3a9..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_derivations.lvs +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ----- DIODE DERIVATIONS ------- -#================================ - -logger.info('Starting DIODE DERIVATIONS') - -diode_exclude = gatpoly.join(nsd_drw).join(trans_drw) - .join(emwind_drw).join(emwihv_drw).join(polyres_drw) - .join(extblock_drw).join(res_drw).join(activ_mask) - .join(recog_esd).join(ind_drw).join(ind_pin) - .join(substrate_drw) - -antenna_d_exc = pwell_block.join(salblock_drw) - .join(nsd_block).join(diode_exclude) - -antenna_d_mk = recog_diode.not(antenna_d_exc) - -# ==== dantenna diode ==== -dantenna_n = activ.and(antenna_d_mk).not(psd_drw).not(nwell_drw) -dantenna_p = pwell.and(antenna_d_mk).covering(dantenna_n) - -# ==== dpantenna diode ==== -dpantenna_p = pactiv.and(antenna_d_mk) -dpantenna_n = nwell_drw.covering(dpantenna_p) - -# ==== schottky_nbl1 diode ==== -schottky_mk = recog_diode.and(thickgateox_drw).not(diode_exclude) - .and(pwell_block).and(ptap_holes).and(nbulay_drw) - .and(salblock_drw).and(nsd_block).and(nwell_holes) - .not(psd_drw).not(pwell).not(diode_exclude) - -schottcky_p_ = cont_drw.and(activ).and(metal1_con) - .and(schottky_mk) - -# schottky_nbl1 is a fixed device (0.3um X 1.0 um) -schottcky_p = schottcky_p_.with_bbox_min(0.3.um).with_bbox_max(1.0.um) -# Using box with area 1x1 to be used as a reference to (m) -schottcky_p_1x1 = schottcky_p.middle(as_boxes).sized(0.499.um) - -schottcky_n = nsd_block.and(activ).covering(schottcky_p) - -# define port for schottcky -schottcky_n_port = activ.interacting(nwell_iso).interacting(schottcky_n).not(schottcky_n.sized(-1.nm)) -schottcky_n_con = cont_drw.and(schottcky_n_port).not_interacting(schottcky_p) -schottcky_sub = ptap.extents.covering(schottcky_p).covering(schottcky_n) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_extraction.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_extraction.lvs deleted file mode 100644 index 13495ccddd..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/diode_extraction.lvs +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ------ DIODE EXTRACTION ------- -#================================ - -logger.info('Starting DIODE EXTRACTION') - -# dantenna diode -logger.info('Extracting dantenna diode') -extract_devices(diode('dantenna', EnDiode), { 'N' => dantenna_n, 'P' => dantenna_p }) - -# dpantenna diode -logger.info('Extracting dpantenna diode') -extract_devices(diode('dpantenna', EnDiode), { 'N' => dpantenna_n, 'P' => dpantenna_p }) - -# schottky_nbl1 diode -logger.info('Extracting schottky_nbl1 diode') -extract_devices(bjt3('schottky_nbl1', Esd3Term), { 'C' => schottcky_sub, - 'B' => schottcky_n_port.extents, - 'E' => schottcky_p_1x1, - 'tC' => ptap, - 'tB' => schottcky_n_port, - 'tE' => schottcky_p_1x1 }) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_connections.lvs deleted file mode 100644 index 7c37e0a53e..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_connections.lvs +++ /dev/null @@ -1,108 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ---- RESISTOR CONNECTIONS ----- -#================================ - -logger.info('Starting ESD CONNECTIONS') - -#====================== -# ----- diode-ESD ----- -#====================== - -# diodevdd_2kv -connect(diodevdd_2kv_e_1x1, diodevdd_2kv_e) -connect(diodevdd_2kv_e, cont_drw) -connect(diodevdd_2kv_b, diodevdd_2kv_tb) -connect(diodevdd_2kv_tb, cont_drw) -connect(diodevdd_2kv_c, cont_drw) - -# diodevdd_4kv -connect(diodevdd_4kv_e_1x1, diodevdd_4kv_e) -connect(diodevdd_4kv_e, diodevdd_4kv_te) -connect(diodevdd_4kv_te, cont_drw) -connect(diodevdd_4kv_b, diodevdd_4kv_tb) -connect(diodevdd_4kv_tb, cont_drw) -connect(diodevdd_4kv_c, cont_drw) - -# diodevss_2kv -connect(diodevss_2kv_e_1x1, diodevss_2kv_e) -connect(diodevss_2kv_e, cont_drw) -connect(diodevss_2kv_b, diodevss_2kv_tb) -connect(diodevss_2kv_tb, cont_drw) -connect(diodevss_2kv_c, cont_drw) - -# diodevss_4kv -connect(diodevss_4kv_e_1x1, diodevss_4kv_e) -connect(diodevss_4kv_e, diodevss_4kv_te) -connect(diodevss_4kv_te, cont_drw) -connect(diodevss_4kv_b, diodevss_4kv_tb) -connect(diodevss_4kv_tb, cont_drw) -connect(diodevss_4kv_c, cont_drw) - -#======================= -# ----- idiode-ESD ----- -#======================= - -# idiodevdd_2kv -connect(idiodevdd_2kv_e_1x1, idiodevdd_2kv_e) -connect(idiodevdd_2kv_e, cont_drw) -connect(idiodevdd_2kv_b, idiodevdd_2kv_tb) -connect(idiodevdd_2kv_tb, cont_drw) -connect(idiodevdd_2kv_c, cont_drw) - -# idiodevdd_4kv -connect(idiodevdd_4kv_e_1x1, idiodevdd_4kv_e) -connect(idiodevdd_4kv_e, idiodevdd_4kv_te) -connect(idiodevdd_4kv_te, cont_drw) -connect(idiodevdd_4kv_b, idiodevdd_4kv_tb) -connect(idiodevdd_4kv_tb, cont_drw) -connect(idiodevdd_4kv_c, cont_drw) - -# idiodevss_2kv -connect(idiodevss_2kv_e_1x1, idiodevss_2kv_e) -connect(idiodevss_2kv_e, cont_drw) -connect(idiodevss_2kv_b, idiodevss_2kv_tb) -connect(idiodevss_2kv_tb, cont_drw) -connect(idiodevss_2kv_c, cont_drw) - -# idiodevss_4kv -connect(idiodevss_4kv_e_1x1, idiodevss_4kv_e) -connect(idiodevss_4kv_e, idiodevss_4kv_te) -connect(idiodevss_4kv_te, cont_drw) -connect(idiodevss_4kv_b, idiodevss_4kv_tb) -connect(idiodevss_4kv_tb, cont_drw) -connect(idiodevss_4kv_c, cont_drw) - -#====================== -# ----- MOSCL-ESD ----- -#====================== - -# nmoscl_2 -connect(nmoscl_2_n, nmoscl_2_n_port) -connect(nmoscl_2_n_port, cont_drw) -connect(nmoscl_2_p, nmoscl_2_p_) -connect(nmoscl_2_p_, ptap) - -# nmoscl_4 -connect(nmoscl_4_n, nmoscl_4_n_port) -connect(nmoscl_4_n_port, cont_drw) -connect(nmoscl_4_p, nmoscl_4_p_) -connect(nmoscl_4_p_, ptap) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_derivations.lvs deleted file mode 100644 index bd78b0ffd8..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_derivations.lvs +++ /dev/null @@ -1,147 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ---- MOS-SAB DERIVATIONS ------ -#================================ - -logger.info('Starting ESD DERIVATIONS') - -# General -esd_exclude = nsd_block.join(nsd_drw).join(trans_drw) - .join(emwind_drw).join(emwihv_drw).join(polyres_drw) - .join(extblock_drw).join(res_drw).join(substrate_drw) - .join(ind_drw).join(ind_pin) - -esd_exc_d = gatpoly.join(thickgateox_drw).join(salblock_drw) - .join(esd_exclude) - -idiodevdd_exc = esd_exc_d.join(nwell_holes) -diodevdd_exc = idiodevdd_exc.join(nbulay_drw).join(pwell_block) - -idiodevss_exc = esd_exc_d.join(nwell_drw.not_interacting(nwell_holes)) - .join(pwell_block) -diodevss_exc = idiodevss_exc.join(nbulay_drw) - -nw_diode = nwell_drw.not_interacting(pwell_block) -nw_idiode = nwell_iso.interacting(pwell_block) - -#====================== -# ----- diode-ESD ----- -#====================== - -# diodevdd_2kv -diodevdd_2kv_e = pactiv.and(nw_diode).and(recog_esd).not(diodevdd_exc) -diodevdd_2kv_e_1x1 = diodevdd_2kv_e.middle.sized(0.499.um) -diodevdd_2kv_b_ = nactiv.and(nw_diode).and(recog_esd).not(diodevdd_exc) -diodevdd_2kv_b = diodevdd_2kv_b_.interacting(diodevdd_2kv_b_.extents.interacting(diodevdd_2kv_e, 1, 1)) -diodevdd_2kv_tb = cont_drw.and(diodevdd_2kv_b).not_interacting(diodevdd_2kv_e) -diodevdd_2kv_c_ = pactiv.and(pwell).and(recog_esd).not(diodevdd_exc) -diodevdd_2kv_c = diodevdd_2kv_c_.interacting(diodevdd_2kv_c_.extents.interacting(diodevdd_2kv_b, 1, 1)) - -# diodevdd_4kv -diodevdd_4kv_b = diodevdd_2kv_b_.interacting(diodevdd_2kv_b_.extents.interacting(diodevdd_2kv_e, 2, 2)) -diodevdd_4kv_c = diodevdd_2kv_c_.interacting(diodevdd_2kv_c_.extents.interacting(diodevdd_4kv_b, 1, 1)) -diodevdd_4kv_e = diodevdd_4kv_b.extents.sized(-0.15.um) -diodevdd_4kv_e_1x1 = diodevdd_4kv_e.middle.sized(0.499.um) -diodevdd_4kv_te = cont_drw.and(diodevdd_2kv_e).not_interacting(diodevdd_4kv_b) -diodevdd_4kv_tb = cont_drw.and(diodevdd_4kv_b).not_interacting(diodevdd_2kv_e) - -# diodevss_2kv -diodevss_2kv_e = nactiv.and(pwell).and(recog_esd).not(diodevss_exc) -diodevss_2kv_e_1x1 = diodevss_2kv_e.middle.sized(0.499.um) -diodevss_2kv_b_ = pactiv.and(pwell).and(recog_esd).not(diodevss_exc) -diodevss_2kv_b = diodevss_2kv_b_.interacting(diodevss_2kv_b_.extents.interacting(diodevss_2kv_e, 1, 1)) -diodevss_2kv_tb = cont_drw.and(diodevss_2kv_b).not_interacting(diodevss_2kv_e) -diodevss_2kv_c_ = nactiv.and(nw_diode).and(recog_esd).not(diodevss_exc) -diodevss_2kv_c = diodevss_2kv_c_.interacting(diodevss_2kv_c_.extents.interacting(diodevss_2kv_b, 1, 1)) - -# diodevss_4kv -diodevss_4kv_b = diodevss_2kv_b_.interacting(diodevss_2kv_b_.extents.interacting(diodevss_2kv_e, 2, 2)) -diodevss_4kv_c = diodevss_2kv_c_.interacting(diodevss_2kv_c_.extents.interacting(diodevss_4kv_b, 1, 1)) -diodevss_4kv_e = diodevss_4kv_b.extents.sized(-0.15.um) -diodevss_4kv_e_1x1 = diodevss_4kv_e.middle.sized(0.499.um) -diodevss_4kv_te = cont_drw.and(diodevss_2kv_e).not_interacting(diodevss_4kv_b) -diodevss_4kv_tb = cont_drw.and(diodevss_4kv_b).not_interacting(diodevss_2kv_e) - -#======================= -# ----- idiode-ESD ----- -#======================= - -# idiodevdd_2kv -idiodevdd_2kv_e = pactiv.and(nw_idiode).and(recog_esd).not(idiodevdd_exc) -idiodevdd_2kv_e_1x1 = idiodevdd_2kv_e.middle.sized(0.499.um) -idiodevdd_2kv_b_ = nactiv.and(nw_idiode).and(recog_esd).not(idiodevdd_exc) -idiodevdd_2kv_b = idiodevdd_2kv_b_.interacting(idiodevdd_2kv_b_.extents.interacting(idiodevdd_2kv_e, 1, 1)) -idiodevdd_2kv_tb = cont_drw.and(idiodevdd_2kv_b).not_interacting(idiodevdd_2kv_e) -idiodevdd_2kv_c_ = pactiv.and(pwell).and(recog_esd).not(idiodevdd_exc) -idiodevdd_2kv_c = idiodevdd_2kv_c_.interacting(idiodevdd_2kv_c_.extents.interacting(idiodevdd_2kv_b, 1, 1)) - -# idiodevdd_4kv -idiodevdd_4kv_b = idiodevdd_2kv_b_.interacting(idiodevdd_2kv_b_.extents.interacting(idiodevdd_2kv_e, 2, 2)) -idiodevdd_4kv_c = idiodevdd_2kv_c_.interacting(idiodevdd_2kv_c_.extents.interacting(idiodevdd_4kv_b, 1, 1)) -idiodevdd_4kv_e = idiodevdd_4kv_b.extents.sized(-0.15.um) -idiodevdd_4kv_e_1x1 = idiodevdd_4kv_e.middle.sized(0.499.um) -idiodevdd_4kv_te = cont_drw.and(idiodevdd_2kv_e).not_interacting(idiodevdd_4kv_b) -idiodevdd_4kv_tb = cont_drw.and(idiodevdd_4kv_b).not_interacting(idiodevdd_2kv_e) - -# idiodevss_2kv -idiodevss_2kv_e = nactiv.and(pwell).and(nbulay_drw).and(recog_esd).not(idiodevss_exc) -idiodevss_2kv_e_1x1 = idiodevss_2kv_e.middle.sized(0.499.um) -idiodevss_2kv_b_ = pactiv.and(pwell).and(nbulay_drw).and(recog_esd).not(idiodevss_exc) -idiodevss_2kv_b = idiodevss_2kv_b_.interacting(idiodevss_2kv_b_.extents.interacting(idiodevss_2kv_e, 1, 1)) -idiodevss_2kv_tb = cont_drw.and(idiodevss_2kv_b).not_interacting(idiodevss_2kv_e) -idiodevss_2kv_c_ = nactiv.and(nwell_iso).and(recog_esd).not(idiodevss_exc) -idiodevss_2kv_c = idiodevss_2kv_c_.interacting(idiodevss_2kv_c_.extents.interacting(idiodevss_2kv_b, 1, 1)) - -# idiodevss_4kv -idiodevss_4kv_b = idiodevss_2kv_b_.interacting(idiodevss_2kv_b_.extents.interacting(idiodevss_2kv_e, 2, 2)) -idiodevss_4kv_c = idiodevss_2kv_c_.interacting(idiodevss_2kv_c_.extents.interacting(idiodevss_4kv_b, 1, 1)) -idiodevss_4kv_e = idiodevss_4kv_b.extents.sized(-0.15.um) -idiodevss_4kv_e_1x1 = idiodevss_4kv_e.middle.sized(0.499.um) -idiodevss_4kv_te = cont_drw.and(idiodevss_2kv_e).not_interacting(idiodevss_4kv_b) -idiodevss_4kv_tb = cont_drw.and(idiodevss_4kv_b).not_interacting(idiodevss_2kv_e) - -#====================== -# ----- MOSCL-ESD ----- -#====================== - -nmoscl_exc = esd_exclude.join(pwell_block) - -# nmoscl_2 -nmoscl_2_patt = glob_to_case_insensitive_glob("nmoscl_2") - -gate_moscl = ngate_hv_base.and(salblock_drw).and(nbulay_drw) -nmoscl_2_n_ = recog_esd.interacting(text_drw.texts(nmoscl_2_patt)) -nmoscl_2_n = nmoscl_2_n_.interacting(gate_moscl, 12) -nmoscl_2_n_port = nwell_drw.and(nmoscl_2_n) -nmoscl_2_p_ = ptap.and(nbulay_drw).inside(nmoscl_2_n_) -# Using box with area 1x1 to be used as a reference to (m) -nmoscl_2_p =nmoscl_2_p_.middle(as_boxes).sized(0.499.um) - -# nmoscl_4 -nmoscl_4_patt = glob_to_case_insensitive_glob("nmoscl_4") - -gate_moscl = ngate_hv_base.and(salblock_drw).and(nbulay_drw) -nmoscl_4_n_ = recog_esd.interacting(text_drw.texts(nmoscl_4_patt)) -nmoscl_4_n = nmoscl_4_n_.interacting(gate_moscl, 24) -nmoscl_4_n_port = nwell_drw.and(nmoscl_4_n) -nmoscl_4_p_ = ptap.and(nbulay_drw).inside(nmoscl_4_n_) -# Using box with area 1x1 to be used as a reference to (m) -nmoscl_4_p =nmoscl_4_p_.middle(as_boxes).sized(0.499.um) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_extraction.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_extraction.lvs deleted file mode 100644 index 585281ee37..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/esd_extraction.lvs +++ /dev/null @@ -1,116 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ----- MOS-SAB EXTRACTION ------ -#================================ - -logger.info('Starting ESD EXTRACTION') - -#====================== -# ----- diode-ESD ----- -#====================== - -# diodevdd_2kv -logger.info('Extracting diodevdd_2kv ESD device') -extract_devices(bjt3('diodevdd_2kv', Esd3Term), { 'C' => diodevdd_2kv_c.extents, - 'B' => diodevdd_2kv_b.extents, - 'E' => diodevdd_2kv_e_1x1, - 'tC' => diodevdd_2kv_c, - 'tB' => diodevdd_2kv_b, - 'tE' => diodevdd_2kv_e_1x1 }) - -# diodevdd_4kv -logger.info('Extracting diodevdd_4kv ESD device') -extract_devices(bjt3('diodevdd_4kv', Esd3Term), { 'C' => diodevdd_4kv_c.extents, - 'B' => diodevdd_4kv_b.extents, - 'E' => diodevdd_4kv_e_1x1, - 'tC' => diodevdd_4kv_c, - 'tB' => diodevdd_4kv_b, - 'tE' => diodevdd_4kv_e_1x1 }) - -# diodevss_2kv -logger.info('Extracting diodevss_2kv ESD device') -extract_devices(bjt3('diodevss_2kv', Esd3Term), { 'C' => diodevss_2kv_c.extents, - 'B' => diodevss_2kv_b.extents, - 'E' => diodevss_2kv_e_1x1, - 'tC' => diodevss_2kv_c, - 'tB' => diodevss_2kv_b, - 'tE' => diodevss_2kv_e_1x1 }) - -# diodevss_4kv -logger.info('Extracting diodevss_4kv ESD device') -extract_devices(bjt3('diodevss_4kv', Esd3Term), { 'C' => diodevss_4kv_c.extents, - 'B' => diodevss_4kv_b.extents, - 'E' => diodevss_4kv_e_1x1, - 'tC' => diodevss_4kv_c, - 'tB' => diodevss_4kv_b, - 'tE' => diodevss_4kv_e_1x1 }) - -#======================= -# ----- idiode-ESD ----- -#======================= - -# idiodevdd_2kv -logger.info('Extracting idiodevdd_2kv ESD device') -extract_devices(bjt3('idiodevdd_2kv', Esd3Term), { 'C' => idiodevdd_2kv_c.extents, - 'B' => idiodevdd_2kv_b.extents, - 'E' => idiodevdd_2kv_e_1x1, - 'tC' => idiodevdd_2kv_c, - 'tB' => idiodevdd_2kv_b, - 'tE' => idiodevdd_2kv_e_1x1 }) - -# idiodevdd_4kv -logger.info('Extracting idiodevdd_4kv ESD device') -extract_devices(bjt3('idiodevdd_4kv', Esd3Term), { 'C' => idiodevdd_4kv_c.extents, - 'B' => idiodevdd_4kv_b.extents, - 'E' => idiodevdd_4kv_e_1x1, - 'tC' => idiodevdd_4kv_c, - 'tB' => idiodevdd_4kv_b, - 'tE' => idiodevdd_4kv_e_1x1 }) - -# idiodevss_2kv -logger.info('Extracting idiodevss_2kv ESD device') -extract_devices(bjt3('idiodevss_2kv', Esd3Term), { 'C' => idiodevss_2kv_c.extents, - 'B' => idiodevss_2kv_b.extents, - 'E' => idiodevss_2kv_e_1x1, - 'tC' => idiodevss_2kv_c, - 'tB' => idiodevss_2kv_b, - 'tE' => idiodevss_2kv_e_1x1 }) - -# idiodevss_4kv -logger.info('Extracting idiodevss_4kv ESD device') -extract_devices(bjt3('idiodevss_4kv', Esd3Term), { 'C' => idiodevss_4kv_c.extents, - 'B' => idiodevss_4kv_b.extents, - 'E' => idiodevss_4kv_e_1x1, - 'tC' => idiodevss_4kv_c, - 'tB' => idiodevss_4kv_b, - 'tE' => idiodevss_4kv_e_1x1 }) - -#====================== -# ----- MOSCL-ESD ----- -#====================== - -# nmoscl_2 -logger.info('Extracting nmoscl_2 ESD device') -extract_devices(diode('nmoscl_2', Esd2Term), { 'N' => nmoscl_2_n, 'P' => nmoscl_2_p }) - -# nmoscl_4 -logger.info('Extracting nmoscl_4 ESD device') -extract_devices(diode('nmoscl_4', Esd2Term), { 'N' => nmoscl_4_n, 'P' => nmoscl_4_p }) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/general_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/general_connections.lvs deleted file mode 100644 index c279ed6800..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/general_connections.lvs +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ----- GENERAL CONNECTIONS ----- -#================================ - -logger.info('Starting GF180 LVS connectivity setup (Inter-layer)') - -# Inter-layer -connect(pwell_sub, pwell) -connect(pwell, ptap) -connect(nwell_drw, ntap) -connect(ntap, cont_drw) -connect(ptap, cont_drw) -connect(poly_con, cont_drw) -connect(nsd_fet, cont_drw) -connect(psd_fet, cont_drw) -connect(cont_drw, metal1_con) -connect(metal1_con, via1_drw) -connect(via1_drw, metal2_con) -connect(metal2_con, via2_drw) -connect(via2_drw, metal3_con) -connect(metal3_con, via3_drw) -connect(via3_drw, metal4_con) -connect(metal4_con, via4_drw) -connect(via4_drw, metal5_con) -connect(metal5_con, topvia1_n_cap) -connect(topvia1_n_cap, topmetal1_con) -connect(topmetal1_con, topvia2_drw) -connect(topvia2_drw, topmetal2_con) - -# salicide connection -connect(nsd_fet, nsd_ptap_abutt) -connect(nsd_ptap_abutt, ptap) -connect(psd_fet, psd_ntap_abutt) -connect(psd_ntap_abutt, ntap) - -# Attaching labels -connect(metal1_con, metal1_text) -connect(metal2_con, metal2_text) -connect(metal3_con, metal3_text) -connect(metal4_con, metal4_text) -connect(metal5_con, metal5_text) -connect(topmetal1_con, topmetal1_text) -connect(topmetal2_con, topmetal2_text) - -logger.info('Starting SG13G2 LVS connectivity setup (Global)') diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/general_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/general_derivations.lvs deleted file mode 100644 index d6780f24a1..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/general_derivations.lvs +++ /dev/null @@ -1,104 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================== -# ------ GENERAL DERIVATIONS ------ -#================================== - -logger.info('Starting general LVS derivations') - -#=== Global Layers === -# === CHIP === -CHIP = case $run_mode -when 'deep' - extent('*') -else - #=== FLAT MODE === - extent.sized(0.0) -end - -# === General Derivations === -# nwell -nwell_iso = nwell_drw.and(nbulay_drw) -nwell_holes = nwell_drw.holes.not(nwell_drw) - -# pwell -pwell_allowed = CHIP.not(pwell_block) -digisub_gap = digisub_drw.not(digisub_drw.sized(-1.nm)) -pwell = pwell_allowed.not(nwell_drw).not(digisub_gap) - -# General pwell -pwell_sub = pwell_allowed.not(digisub_drw).not(nbulay_drw.interacting(nwell_holes)) - -# n & p activ -nactiv = activ.not(psd_drw.join(nsd_block)) -pactiv = activ.and(psd_drw) - -# res/cap exclusion -res_mk = polyres_drw.join(res_drw) -poly_con = gatpoly.not(res_mk) -metal1_con = metal1.not(metal1_res) -metal2_con = metal2.not(metal2_res) -metal3_con = metal3.not(metal3_res) -metal4_con = metal4.not(metal4_res) -metal5_con = metal5.not(metal5_res) -topmetal1_con = topmetal1.not(topmetal1_res).not(ind_drw) -topmetal2_con = topmetal2.not(topmetal2_res).not(ind_drw) - -# Gate FETs -tgate = gatpoly.and(activ).not(res_mk) -ngate = nactiv.and(tgate) -pgate = pactiv.and(tgate) -ngate_lv_base = ngate.not(thickgateox_drw) -pgate_lv_base = pgate.not(thickgateox_drw) -ngate_hv_base = ngate.and(thickgateox_drw) -pgate_hv_base = pgate.and(thickgateox_drw) - -# S/D FETs -nsd_fet = nactiv.not(nwell_drw).interacting(ngate).not(ngate).not_interacting(res_mk) -psd_fet = pactiv.and(nwell_drw).interacting(pgate).not(pgate).not_interacting(res_mk) - - -# n1/p1 taps labels -well_patt = glob_to_case_insensitive_glob("well") -sub_patt = glob_to_case_insensitive_glob("sub!") - -ntap1_lbl = text_drw.texts(well_patt) -ntap1_mk = nwell_drw.interacting(ntap1_lbl) - -ptap1_lbl = text_drw.texts(sub_patt) -ptap1_mk = substrate_drw.and(pwell).interacting(ptap1_lbl) - -# n & p taps (short connections) -ntap = nactiv.and(nwell_drw).not(recog_diode).not(gatpoly).not(ntap1_mk) -ptap = pactiv.and(pwell).not(ptap1_mk).not(recog_diode).not(gatpoly) -ptap_holes = ptap.holes -ntap_holes = ntap.holes - -# S/D (salicide) -nsd_sal = nsd_fet.not(salblock_drw) -psd_sal = psd_fet.not(salblock_drw) - -# n & p taps (salicide) -ntap_sal = ntap.not(salblock_drw) -ptap_sal = ptap.not(salblock_drw) - -# n/p SD abutted with n/p taps (salicide) -nsd_ptap_abutt = nsd_sal.edges.and(ptap_sal.edges).extended(:in => 1.nm, :out => 1.nm) -psd_ntap_abutt = psd_sal.edges.and(ntap_sal.edges).extended(:in => 1.nm, :out => 1.nm) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/globals.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/globals.lvs deleted file mode 100644 index 2f49defc12..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/globals.lvs +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#=========================================== -# --------------- Varaibles ---------------- -#=========================================== - -# Prefix for each device -PREFIX_MAP = { - 'sg13_lv_nmos' => 'M', - 'sg13_hv_nmos' => 'M', - 'sg13_lv_pmos' => 'M', - 'sg13_hv_pmos' => 'M', - 'rfnmos' => 'M', - 'rfnmosHV' => 'M', - 'rfpmos' => 'M', - 'rfpmosHV' => 'M', - 'npn13G2' => 'Q', - 'npn13G2L' => 'Q', - 'npn13G2V' => 'Q', - 'pnpMPA' => 'Q', - 'dantenna' => 'D', - 'dpantenna' => 'D', - 'schottky_nbl1' => 'D', - 'rsil' => 'R', - 'rppd' => 'R', - 'rhigh' => 'R', - 'lvsres' => 'R', - 'SVaricap' => 'C', - 'cap_cmim' => 'C', - 'rfcmim' => 'C', - 'diodevss_4kv' => 'D', - 'diodevss_2kv' => 'D', - 'diodevdd_4kv' => 'D', - 'diodevdd_2kv' => 'D', - 'idiodevss_4kv' => 'D', - 'idiodevss_2kv' => 'D', - 'idiodevdd_4kv' => 'D', - 'idiodevdd_2kv' => 'D', - 'nmoscl_2' => 'D', - 'nmoscl_4' => 'D', - 'ptap1' => 'R', - 'ntap1' => 'R', - 'inductor2' => 'L', - 'inductor3' => 'L' -}.freeze - -# Prefix for devices will be customized -CUSTOM_READER = %w[M C R Q L D].freeze - -# List of poly-resistors -RES_DEV = ['rsil', 'rppd', 'rhigh'] diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_connections.lvs deleted file mode 100644 index 3ad62294b6..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_connections.lvs +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#=================================== -# ------ Inductor CONNECTIONS ------ -#=================================== - -logger.info('Starting Inductor CONNECTIONS') - -# ind2 -connect(ind2_ports, ind_pin) -connect(ind_pin, ind_text) -connect(ind_pin, topmetal2_con) -connect(ind2_sub, pwell) -connect(ind2_sub, nwell_drw) - -# ind3 -connect(ind3_ports, ind_pin) -connect(ind_pin, topmetal1_con) -connect(ind3_sub, pwell) -connect(ind3_sub, nwell_drw) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_derivations.lvs deleted file mode 100644 index 079ffaddc9..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_derivations.lvs +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#=============================== -# ---- Inductor DERIVATIONS ---- -#=============================== - -logger.info('Starting Inductor DERIVATIONS') - -ind_exc = gatpoly.join(nsd_drw).join(nbulay_drw) - .join(thickgateox_drw).join(emwind_drw).join(emwihv_drw) - .join(salblock_drw).join(polyres_drw).join(mim_drw) - .join(extblock_drw).join(res_drw).join(activ_mask) - .join(recog_diode).join(recog_esd).join(substrate_drw) - -# General -la_patt = glob_to_case_insensitive_glob("LA") -lb_patt = glob_to_case_insensitive_glob("LB") -lc_patt = glob_to_case_insensitive_glob("LC") -ind2_patt = glob_to_case_insensitive_glob("inductor2*") -ind3_patt = glob_to_case_insensitive_glob("inductor3*") - -ind_edges = ind_drw.edges -ind_core_ = topmetal2.join(topmetal1).and(ind_drw).merged.not(ind_exc) -ind_ports_ = ind_pin.and(ind_core_).interacting(ind_edges) -ind_port_la = ind_ports_.interacting(ind_text.texts(la_patt)) -ind_la_tm1 = ind_port_la.and(topmetal1) -ind_port_lb = ind_ports_.interacting(ind_text.texts(lb_patt)) -ind_lb_tm1 = ind_port_lb.and(topmetal1) -ind_port_lc = ind_ports_.interacting(ind_text.texts(lc_patt)) -ind_lc_tm2 = ind_port_lc.and(topmetal2) - -# inductor2 -ind2_ports = ind_port_la.join(ind_port_lb) -ind2_core = ind_core_.interacting(ind_port_la, 1).interacting(ind_port_lb, 1) -ind2_mk_ = ind_drw.interacting(text_drw.texts(ind2_patt)) -ind2_mk = ind2_mk_.interacting(ind2_core).interacting(ind2_ports).not_interacting(ind_port_lc) -ind2_sub1 = pwell.and(ind_drw).interacting(ind2_core) -ind2_sub2 = pwell_block.and(ind_drw).interacting(ind2_core).sized(1.nm) -ind2_well = nwell_drw.and(ind_drw).interacting(ind2_core).sized(-1.nm) -ind2_sub = ind2_sub1.join(ind2_sub2).join(ind2_well) -# inductor3 -ind3_ports = ind_la_tm1.join(ind_lb_tm1).join(ind_lc_tm2) -ind3_core = ind_core_.interacting(ind_lb_tm1, 1).interacting(ind_lb_tm1, 1).interacting(ind_lc_tm2, 1) -ind3_mk_ = ind_drw.interacting(text_drw.texts(ind3_patt)) -ind3_mk = ind3_mk_.interacting(ind3_core).interacting(ind3_ports) -ind3_sub1 = pwell.and(ind_drw).interacting(ind3_core) -ind3_sub2 = pwell_block.and(ind_drw).interacting(ind3_core).sized(1.nm) -ind3_well = nwell_drw.and(ind_drw).interacting(ind3_core).sized(-1.nm) -ind3_sub = ind3_sub1.join(ind3_sub2).join(ind3_well) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_extraction.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_extraction.lvs deleted file mode 100644 index 3d2b6b494c..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/ind_extraction.lvs +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ----- Inductor EXTRACTION ----- -#================================ - -logger.info('Starting Inductor EXTRACTION') - -# ind2 -logger.info('Extracting Inductor2 device') -extract_devices(GeneralNTerminalExtractor.new('inductor', 2), { - 'core' => ind2_core, - 'ports' => ind2_ports, - 'meas_mk' => topmetal2.and(ind2_core), - 'dev_mk' => ind2_mk, - 'sub_mk' => ind2_sub - }) - -# ind3 -logger.info('Extracting Inductor3 device') -extract_devices(GeneralNTerminalExtractor.new('inductor3', 3), { - 'core' => ind3_core, - 'ports' => ind3_ports, - 'meas_mk' => topmetal2.and(ind3_core), - 'dev_mk' => ind3_mk, - 'sub_mk' => ind3_sub - }) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/layers_definitions.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/layers_definitions.lvs deleted file mode 100644 index e6cf6ba5de..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/layers_definitions.lvs +++ /dev/null @@ -1,1856 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================================ -#------------- LAYERS DEFINITIONS --------------- -#================================================ - -polygons_count = 0 -logger.info('Read in polygons from layers.') - -def get_polygons(lay_no, lay_dt) - if $run_mode == 'deep' - polygons(lay_no, lay_dt) - else - polygons(lay_no, lay_dt).merged - end -end - -nodrc_drw = get_polygons(62, 0) -count = nodrc_drw.count() -logger.info("nodrc_drw has #{count} polygons") -polygons_count += count - -activ_drw = get_polygons(1, 0) -activ_drw = activ_drw.not(nodrc_drw) -count = activ_drw.count() -logger.info("activ_drw has #{count} polygons") -polygons_count += count - -activ_filler = get_polygons(1, 22) -activ_filler = activ_filler.not(nodrc_drw) -count = activ_filler.count() -logger.info("activ_filler has #{count} polygons") -polygons_count += count - -# activ org -activ = activ_drw.join(activ_filler) -count = activ.count() -logger.info("activ has #{count} polygons") - -activ_pin = get_polygons(1, 2) -activ_pin = activ_pin.not(nodrc_drw) -count = activ_pin.count() -logger.info("activ_pin has #{count} polygons") -polygons_count += count - -activ_mask = get_polygons(1, 20) -activ_mask = activ_mask.not(nodrc_drw) -count = activ_mask.count() -logger.info("activ_mask has #{count} polygons") -polygons_count += count - -activ_nofill = get_polygons(1, 23) -activ_nofill = activ_nofill.not(nodrc_drw) -count = activ_nofill.count() -logger.info("activ_nofill has #{count} polygons") -polygons_count += count - -activ_OPC = get_polygons(1, 26) -activ_OPC = activ_OPC.not(nodrc_drw) -count = activ_OPC.count() -logger.info("activ_OPC has #{count} polygons") -polygons_count += count - -activ_iOPC = get_polygons(1, 27) -activ_iOPC = activ_iOPC.not(nodrc_drw) -count = activ_iOPC.count() -logger.info("activ_iOPC has #{count} polygons") -polygons_count += count - -activ_noqrc = get_polygons(1, 28) -activ_noqrc = activ_noqrc.not(nodrc_drw) -count = activ_noqrc.count() -logger.info("activ_noqrc has #{count} polygons") -polygons_count += count - -biwind_drw = get_polygons(3, 0) -biwind_drw = biwind_drw.not(nodrc_drw) -count = biwind_drw.count() -logger.info("biwind_drw has #{count} polygons") -polygons_count += count - -biwind_OPC = get_polygons(3, 26) -biwind_OPC = biwind_OPC.not(nodrc_drw) -count = biwind_OPC.count() -logger.info("biwind_OPC has #{count} polygons") -polygons_count += count - -gatpoly_drw = get_polygons(5, 0) -gatpoly_drw = gatpoly_drw.not(nodrc_drw) -count = gatpoly_drw.count() -logger.info("gatpoly_drw has #{count} polygons") -polygons_count += count - -gatpoly_filler = get_polygons(5, 22) -gatpoly_filler = gatpoly_filler.not(nodrc_drw) -count = gatpoly_filler.count() -logger.info("gatpoly_filler has #{count} polygons") -polygons_count += count - -# gatpoly org -gatpoly = gatpoly_drw.join(gatpoly_filler) -count = gatpoly.count() -logger.info("gatpoly has #{count} polygons") - -gatpoly_pin = get_polygons(5, 2) -gatpoly_pin = gatpoly_pin.not(nodrc_drw) -count = gatpoly_pin.count() -logger.info("gatpoly_pin has #{count} polygons") -polygons_count += count - -gatpoly_nofill = get_polygons(5, 23) -gatpoly_nofill = gatpoly_nofill.not(nodrc_drw) -count = gatpoly_nofill.count() -logger.info("gatpoly_nofill has #{count} polygons") -polygons_count += count - -gatpoly_OPC = get_polygons(5, 26) -gatpoly_OPC = gatpoly_OPC.not(nodrc_drw) -count = gatpoly_OPC.count() -logger.info("gatpoly_OPC has #{count} polygons") -polygons_count += count - -gatpoly_iOPC = get_polygons(5, 27) -gatpoly_iOPC = gatpoly_iOPC.not(nodrc_drw) -count = gatpoly_iOPC.count() -logger.info("gatpoly_iOPC has #{count} polygons") -polygons_count += count - -gatpoly_noqrc = get_polygons(5, 28) -gatpoly_noqrc = gatpoly_noqrc.not(nodrc_drw) -count = gatpoly_noqrc.count() -logger.info("gatpoly_noqrc has #{count} polygons") -polygons_count += count - -cont_drw = get_polygons(6, 0) -cont_drw = cont_drw.not(nodrc_drw) -count = cont_drw.count() -logger.info("cont_drw has #{count} polygons") -polygons_count += count - -cont_OPC = get_polygons(6, 26) -cont_OPC = cont_OPC.not(nodrc_drw) -count = cont_OPC.count() -logger.info("cont_OPC has #{count} polygons") -polygons_count += count - -nsd_drw = get_polygons(7, 0) -nsd_drw = nsd_drw.not(nodrc_drw) -count = nsd_drw.count() -logger.info("nsd_drw has #{count} polygons") -polygons_count += count - -nsd_block = get_polygons(7, 21) -nsd_block = nsd_block.not(nodrc_drw) -count = nsd_block.count() -logger.info("nsd_block has #{count} polygons") -polygons_count += count - -metal1_drw = get_polygons(8, 0) -metal1_drw = metal1_drw.not(nodrc_drw) -count = metal1_drw.count() -logger.info("metal1_drw has #{count} polygons") -polygons_count += count - -metal1_filler = get_polygons(8, 22) -metal1_filler = metal1_filler.not(nodrc_drw) -count = metal1_filler.count() -logger.info("metal1_filler has #{count} polygons") -polygons_count += count - -metal1_slit = get_polygons(8, 24) -metal1_slit = metal1_slit.not(nodrc_drw) -count = metal1_slit.count() -logger.info("metal1_slit has #{count} polygons") -polygons_count += count - -# metal1 org -metal1 = metal1_drw.join(metal1_filler).not(metal1_slit) -count = metal1.count() -logger.info("metal1 has #{count} polygons") - -metal1_pin = get_polygons(8, 2) -metal1_pin = metal1_pin.not(nodrc_drw) -count = metal1_pin.count() -logger.info("metal1_pin has #{count} polygons") -polygons_count += count - -metal1_mask = get_polygons(8, 20) -metal1_mask = metal1_mask.not(nodrc_drw) -count = metal1_mask.count() -logger.info("metal1_mask has #{count} polygons") -polygons_count += count - -metal1_nofill = get_polygons(8, 23) -metal1_nofill = metal1_nofill.not(nodrc_drw) -count = metal1_nofill.count() -logger.info("metal1_nofill has #{count} polygons") -polygons_count += count - -metal1_text = labels(8, 25) -metal1_text = metal1_text.not(nodrc_drw) -count = metal1_text.count() -logger.info("metal1_text has #{count} polygons") -polygons_count += count - -metal1_OPC = get_polygons(8, 26) -metal1_OPC = metal1_OPC.not(nodrc_drw) -count = metal1_OPC.count() -logger.info("metal1_OPC has #{count} polygons") -polygons_count += count - -metal1_noqrc = get_polygons(8, 28) -metal1_noqrc = metal1_noqrc.not(nodrc_drw) -count = metal1_noqrc.count() -logger.info("metal1_noqrc has #{count} polygons") -polygons_count += count - -metal1_res = get_polygons(8, 29) -metal1_res = metal1_res.not(nodrc_drw) -count = metal1_res.count() -logger.info("metal1_res has #{count} polygons") -polygons_count += count - -metal1_iprobe = get_polygons(8, 33) -metal1_iprobe = metal1_iprobe.not(nodrc_drw) -count = metal1_iprobe.count() -logger.info("metal1_iprobe has #{count} polygons") -polygons_count += count - -metal1_diffprb = get_polygons(8, 34) -metal1_diffprb = metal1_diffprb.not(nodrc_drw) -count = metal1_diffprb.count() -logger.info("metal1_diffprb has #{count} polygons") -polygons_count += count - -passiv_drw = get_polygons(9, 0) -passiv_drw = passiv_drw.not(nodrc_drw) -count = passiv_drw.count() -logger.info("passiv_drw has #{count} polygons") -polygons_count += count - -passiv_pin = get_polygons(9, 2) -passiv_pin = passiv_pin.not(nodrc_drw) -count = passiv_pin.count() -logger.info("passiv_pin has #{count} polygons") -polygons_count += count - -passiv_sbump = get_polygons(9, 36) -passiv_sbump = passiv_sbump.not(nodrc_drw) -count = passiv_sbump.count() -logger.info("passiv_sbump has #{count} polygons") -polygons_count += count - -passiv_pillar = get_polygons(9, 35) -passiv_pillar = passiv_pillar.not(nodrc_drw) -count = passiv_pillar.count() -logger.info("passiv_pillar has #{count} polygons") -polygons_count += count - -passiv_pdl = get_polygons(9, 40) -passiv_pdl = passiv_pdl.not(nodrc_drw) -count = passiv_pdl.count() -logger.info("passiv_pdl has #{count} polygons") -polygons_count += count - -metal2_drw = get_polygons(10, 0) -metal2_drw = metal2_drw.not(nodrc_drw) -count = metal2_drw.count() -logger.info("metal2_drw has #{count} polygons") -polygons_count += count - -metal2_filler = get_polygons(10, 22) -metal2_filler = metal2_filler.not(nodrc_drw) -count = metal2_filler.count() -logger.info("metal2_filler has #{count} polygons") -polygons_count += count - -metal2_slit = get_polygons(10, 24) -metal2_slit = metal2_slit.not(nodrc_drw) -count = metal2_slit.count() -logger.info("metal2_slit has #{count} polygons") -polygons_count += count - -# metal2 org -metal2 = metal2_drw.join(metal2_filler).not(metal2_slit) -count = metal2.count() -logger.info("metal2 has #{count} polygons") - -metal2_pin = get_polygons(10, 2) -metal2_pin = metal2_pin.not(nodrc_drw) -count = metal2_pin.count() -logger.info("metal2_pin has #{count} polygons") -polygons_count += count - -metal2_mask = get_polygons(10, 20) -metal2_mask = metal2_mask.not(nodrc_drw) -count = metal2_mask.count() -logger.info("metal2_mask has #{count} polygons") -polygons_count += count - -metal2_nofill = get_polygons(10, 23) -metal2_nofill = metal2_nofill.not(nodrc_drw) -count = metal2_nofill.count() -logger.info("metal2_nofill has #{count} polygons") -polygons_count += count - -metal2_text = labels(10, 25) -metal2_text = metal2_text.not(nodrc_drw) -count = metal2_text.count() -logger.info("metal2_text has #{count} polygons") -polygons_count += count - -metal2_OPC = get_polygons(10, 26) -metal2_OPC = metal2_OPC.not(nodrc_drw) -count = metal2_OPC.count() -logger.info("metal2_OPC has #{count} polygons") -polygons_count += count - -metal2_noqrc = get_polygons(10, 28) -metal2_noqrc = metal2_noqrc.not(nodrc_drw) -count = metal2_noqrc.count() -logger.info("metal2_noqrc has #{count} polygons") -polygons_count += count - -metal2_res = get_polygons(10, 29) -metal2_res = metal2_res.not(nodrc_drw) -count = metal2_res.count() -logger.info("metal2_res has #{count} polygons") -polygons_count += count - -metal2_iprobe = get_polygons(10, 33) -metal2_iprobe = metal2_iprobe.not(nodrc_drw) -count = metal2_iprobe.count() -logger.info("metal2_iprobe has #{count} polygons") -polygons_count += count - -metal2_diffprb = get_polygons(10, 34) -metal2_diffprb = metal2_diffprb.not(nodrc_drw) -count = metal2_diffprb.count() -logger.info("metal2_diffprb has #{count} polygons") -polygons_count += count - -baspoly_drw = get_polygons(13, 0) -baspoly_drw = baspoly_drw.not(nodrc_drw) -count = baspoly_drw.count() -logger.info("baspoly_drw has #{count} polygons") -polygons_count += count - -baspoly_pin = get_polygons(13, 2) -baspoly_pin = baspoly_pin.not(nodrc_drw) -count = baspoly_pin.count() -logger.info("baspoly_pin has #{count} polygons") -polygons_count += count - -psd_drw = get_polygons(14, 0) -psd_drw = psd_drw.not(nodrc_drw) -count = psd_drw.count() -logger.info("psd_drw has #{count} polygons") -polygons_count += count - -nldb_drw = get_polygons(15, 0) -nldb_drw = nldb_drw.not(nodrc_drw) -count = nldb_drw.count() -logger.info("nldb_drw has #{count} polygons") -polygons_count += count - -digibnd_drw = get_polygons(16, 0) -digibnd_drw = digibnd_drw.not(nodrc_drw) -count = digibnd_drw.count() -logger.info("digibnd_drw has #{count} polygons") -polygons_count += count - -via1_drw = get_polygons(19, 0) -via1_drw = via1_drw.not(nodrc_drw) -count = via1_drw.count() -logger.info("via1_drw has #{count} polygons") -polygons_count += count - -backmetal1_drw = get_polygons(20, 0) -backmetal1_drw = backmetal1_drw.not(nodrc_drw) -count = backmetal1_drw.count() -logger.info("backmetal1_drw has #{count} polygons") -polygons_count += count - -backmetal1_pin = get_polygons(20, 2) -backmetal1_pin = backmetal1_pin.not(nodrc_drw) -count = backmetal1_pin.count() -logger.info("backmetal1_pin has #{count} polygons") -polygons_count += count - -backmetal1_mask = get_polygons(20, 20) -backmetal1_mask = backmetal1_mask.not(nodrc_drw) -count = backmetal1_mask.count() -logger.info("backmetal1_mask has #{count} polygons") -polygons_count += count - -backmetal1_filler = get_polygons(20, 22) -backmetal1_filler = backmetal1_filler.not(nodrc_drw) -count = backmetal1_filler.count() -logger.info("backmetal1_filler has #{count} polygons") -polygons_count += count - -backmetal1_nofill = get_polygons(20, 23) -backmetal1_nofill = backmetal1_nofill.not(nodrc_drw) -count = backmetal1_nofill.count() -logger.info("backmetal1_nofill has #{count} polygons") -polygons_count += count - -backmetal1_slit = get_polygons(20, 24) -backmetal1_slit = backmetal1_slit.not(nodrc_drw) -count = backmetal1_slit.count() -logger.info("backmetal1_slit has #{count} polygons") -polygons_count += count - -backmetal1_text = labels(20, 25) -backmetal1_text = backmetal1_text.not(nodrc_drw) -count = backmetal1_text.count() -logger.info("backmetal1_text has #{count} polygons") -polygons_count += count - -backmetal1_OPC = get_polygons(20, 26) -backmetal1_OPC = backmetal1_OPC.not(nodrc_drw) -count = backmetal1_OPC.count() -logger.info("backmetal1_OPC has #{count} polygons") -polygons_count += count - -backmetal1_noqrc = get_polygons(20, 28) -backmetal1_noqrc = backmetal1_noqrc.not(nodrc_drw) -count = backmetal1_noqrc.count() -logger.info("backmetal1_noqrc has #{count} polygons") -polygons_count += count - -backmetal1_res = get_polygons(20, 29) -backmetal1_res = backmetal1_res.not(nodrc_drw) -count = backmetal1_res.count() -logger.info("backmetal1_res has #{count} polygons") -polygons_count += count - -backmetal1_iprobe = get_polygons(20, 33) -backmetal1_iprobe = backmetal1_iprobe.not(nodrc_drw) -count = backmetal1_iprobe.count() -logger.info("backmetal1_iprobe has #{count} polygons") -polygons_count += count - -backmetal1_diffprb = get_polygons(20, 34) -backmetal1_diffprb = backmetal1_diffprb.not(nodrc_drw) -count = backmetal1_diffprb.count() -logger.info("backmetal1_diffprb has #{count} polygons") -polygons_count += count - -backpassiv_drw = get_polygons(23, 0) -backpassiv_drw = backpassiv_drw.not(nodrc_drw) -count = backpassiv_drw.count() -logger.info("backpassiv_drw has #{count} polygons") -polygons_count += count - -res_drw = get_polygons(24, 0) -res_drw = res_drw.not(nodrc_drw) -count = res_drw.count() -logger.info("res_drw has #{count} polygons") -polygons_count += count - -sram_drw = get_polygons(25, 0) -sram_drw = sram_drw.not(nodrc_drw) -count = sram_drw.count() -logger.info("sram_drw has #{count} polygons") -polygons_count += count - -trans_drw = get_polygons(26, 0) -trans_drw = trans_drw.not(nodrc_drw) -count = trans_drw.count() -logger.info("trans_drw has #{count} polygons") -polygons_count += count - -ind_drw = get_polygons(27, 0) -ind_drw = ind_drw.not(nodrc_drw) -count = ind_drw.count() -logger.info("ind_drw has #{count} polygons") -polygons_count += count - -ind_pin = get_polygons(27, 2) -ind_pin = ind_pin.not(nodrc_drw) -count = ind_pin.count() -logger.info("ind_pin has #{count} polygons") -polygons_count += count - -ind_text = labels(27, 25) -ind_text = ind_text.not(nodrc_drw) -count = ind_text.count() -logger.info("ind_text has #{count} polygons") -polygons_count += count - -salblock_drw = get_polygons(28, 0) -salblock_drw = salblock_drw.not(nodrc_drw) -count = salblock_drw.count() -logger.info("salblock_drw has #{count} polygons") -polygons_count += count - -via2_drw = get_polygons(29, 0) -via2_drw = via2_drw.not(nodrc_drw) -count = via2_drw.count() -logger.info("via2_drw has #{count} polygons") -polygons_count += count - -metal3_drw = get_polygons(30, 0) -metal3_drw = metal3_drw.not(nodrc_drw) -count = metal3_drw.count() -logger.info("metal3_drw has #{count} polygons") -polygons_count += count - -metal3_filler = get_polygons(30, 22) -metal3_filler = metal3_filler.not(nodrc_drw) -count = metal3_filler.count() -logger.info("metal3_filler has #{count} polygons") -polygons_count += count - -metal3_slit = get_polygons(30, 24) -metal3_slit = metal3_slit.not(nodrc_drw) -count = metal3_slit.count() -logger.info("metal3_slit has #{count} polygons") -polygons_count += count - -# metal3 org -metal3 = metal3_drw.join(metal3_filler).not(metal3_slit) -count = metal3.count() -logger.info("metal3 has #{count} polygons") - -metal3_pin = get_polygons(30, 2) -metal3_pin = metal3_pin.not(nodrc_drw) -count = metal3_pin.count() -logger.info("metal3_pin has #{count} polygons") -polygons_count += count - -metal3_mask = get_polygons(30, 20) -metal3_mask = metal3_mask.not(nodrc_drw) -count = metal3_mask.count() -logger.info("metal3_mask has #{count} polygons") -polygons_count += count - -metal3_nofill = get_polygons(30, 23) -metal3_nofill = metal3_nofill.not(nodrc_drw) -count = metal3_nofill.count() -logger.info("metal3_nofill has #{count} polygons") -polygons_count += count - -metal3_text = labels(30, 25) -metal3_text = metal3_text.not(nodrc_drw) -count = metal3_text.count() -logger.info("metal3_text has #{count} polygons") -polygons_count += count - -metal3_OPC = get_polygons(30, 26) -metal3_OPC = metal3_OPC.not(nodrc_drw) -count = metal3_OPC.count() -logger.info("metal3_OPC has #{count} polygons") -polygons_count += count - -metal3_noqrc = get_polygons(30, 28) -metal3_noqrc = metal3_noqrc.not(nodrc_drw) -count = metal3_noqrc.count() -logger.info("metal3_noqrc has #{count} polygons") -polygons_count += count - -metal3_res = get_polygons(30, 29) -metal3_res = metal3_res.not(nodrc_drw) -count = metal3_res.count() -logger.info("metal3_res has #{count} polygons") -polygons_count += count - -metal3_iprobe = get_polygons(30, 33) -metal3_iprobe = metal3_iprobe.not(nodrc_drw) -count = metal3_iprobe.count() -logger.info("metal3_iprobe has #{count} polygons") -polygons_count += count - -metal3_diffprb = get_polygons(30, 34) -metal3_diffprb = metal3_diffprb.not(nodrc_drw) -count = metal3_diffprb.count() -logger.info("metal3_diffprb has #{count} polygons") -polygons_count += count - -nwell_drw = get_polygons(31, 0) -nwell_drw = nwell_drw.not(nodrc_drw) -count = nwell_drw.count() -logger.info("nwell_drw has #{count} polygons") -polygons_count += count - -nwell_pin = get_polygons(31, 2) -nwell_pin = nwell_pin.not(nodrc_drw) -count = nwell_pin.count() -logger.info("nwell_pin has #{count} polygons") -polygons_count += count - -nbulay_drw = get_polygons(32, 0) -nbulay_drw = nbulay_drw.not(nodrc_drw) -count = nbulay_drw.count() -logger.info("nbulay_drw has #{count} polygons") -polygons_count += count - -nbulay_pin = get_polygons(32, 2) -nbulay_pin = nbulay_pin.not(nodrc_drw) -count = nbulay_pin.count() -logger.info("nbulay_pin has #{count} polygons") -polygons_count += count - -nbulay_block = get_polygons(32, 21) -nbulay_block = nbulay_block.not(nodrc_drw) -count = nbulay_block.count() -logger.info("nbulay_block has #{count} polygons") -polygons_count += count - -emwind_drw = get_polygons(33, 0) -emwind_drw = emwind_drw.not(nodrc_drw) -count = emwind_drw.count() -logger.info("emwind_drw has #{count} polygons") -polygons_count += count - -emwind_OPC = get_polygons(33, 26) -emwind_OPC = emwind_OPC.not(nodrc_drw) -count = emwind_OPC.count() -logger.info("emwind_OPC has #{count} polygons") -polygons_count += count - -deepco_drw = get_polygons(35, 0) -deepco_drw = deepco_drw.not(nodrc_drw) -count = deepco_drw.count() -logger.info("deepco_drw has #{count} polygons") -polygons_count += count - -mim_drw = get_polygons(36, 0) -mim_drw = mim_drw.not(nodrc_drw) -count = mim_drw.count() -logger.info("mim_drw has #{count} polygons") -polygons_count += count - -edgeseal_drw = get_polygons(39, 0) -edgeseal_drw = edgeseal_drw.not(nodrc_drw) -count = edgeseal_drw.count() -logger.info("edgeseal_drw has #{count} polygons") -polygons_count += count - -substrate_drw = get_polygons(40, 0) -substrate_drw = substrate_drw.not(nodrc_drw) -count = substrate_drw.count() -logger.info("substrate_drw has #{count} polygons") -polygons_count += count - -substrate_text = labels(40, 25) -substrate_text = substrate_text.not(nodrc_drw) -count = substrate_text.count() -logger.info("substrate_text has #{count} polygons") -polygons_count += count - -dfpad_drw = get_polygons(41, 0) -dfpad_drw = dfpad_drw.not(nodrc_drw) -count = dfpad_drw.count() -logger.info("dfpad_drw has #{count} polygons") -polygons_count += count - -dfpad_pillar = get_polygons(41, 35) -dfpad_pillar = dfpad_pillar.not(nodrc_drw) -count = dfpad_pillar.count() -logger.info("dfpad_pillar has #{count} polygons") -polygons_count += count - -dfpad_sbump = get_polygons(41, 36) -dfpad_sbump = dfpad_sbump.not(nodrc_drw) -count = dfpad_sbump.count() -logger.info("dfpad_sbump has #{count} polygons") -polygons_count += count - -thickgateox_drw = get_polygons(44, 0) -thickgateox_drw = thickgateox_drw.not(nodrc_drw) -count = thickgateox_drw.count() -logger.info("thickgateox_drw has #{count} polygons") -polygons_count += count - -pldb_drw = get_polygons(45, 0) -pldb_drw = pldb_drw.not(nodrc_drw) -count = pldb_drw.count() -logger.info("pldb_drw has #{count} polygons") -polygons_count += count - -pwell_drw = get_polygons(46, 0) -pwell_drw = pwell_drw.not(nodrc_drw) -count = pwell_drw.count() -logger.info("pwell_drw has #{count} polygons") -polygons_count += count - -pwell_pin = get_polygons(46, 2) -pwell_pin = pwell_pin.not(nodrc_drw) -count = pwell_pin.count() -logger.info("pwell_pin has #{count} polygons") -polygons_count += count - -pwell_block = get_polygons(46, 21) -pwell_block = pwell_block.not(nodrc_drw) -count = pwell_block.count() -logger.info("pwell_block has #{count} polygons") -polygons_count += count - -ic_drw = get_polygons(48, 0) -ic_drw = ic_drw.not(nodrc_drw) -count = ic_drw.count() -logger.info("ic_drw has #{count} polygons") -polygons_count += count - -via3_drw = get_polygons(49, 0) -via3_drw = via3_drw.not(nodrc_drw) -count = via3_drw.count() -logger.info("via3_drw has #{count} polygons") -polygons_count += count - -metal4_drw = get_polygons(50, 0) -metal4_drw = metal4_drw.not(nodrc_drw) -count = metal4_drw.count() -logger.info("metal4_drw has #{count} polygons") -polygons_count += count - -metal4_filler = get_polygons(50, 22) -metal4_filler = metal4_filler.not(nodrc_drw) -count = metal4_filler.count() -logger.info("metal4_filler has #{count} polygons") -polygons_count += count - -metal4_slit = get_polygons(50, 24) -metal4_slit = metal4_slit.not(nodrc_drw) -count = metal4_slit.count() -logger.info("metal4_slit has #{count} polygons") -polygons_count += count - -# metal4 org -metal4 = metal4_drw.join(metal4_filler).not(metal4_slit) -count = metal4.count() -logger.info("metal4 has #{count} polygons") - -metal4_pin = get_polygons(50, 2) -metal4_pin = metal4_pin.not(nodrc_drw) -count = metal4_pin.count() -logger.info("metal4_pin has #{count} polygons") -polygons_count += count - -metal4_mask = get_polygons(50, 20) -metal4_mask = metal4_mask.not(nodrc_drw) -count = metal4_mask.count() -logger.info("metal4_mask has #{count} polygons") -polygons_count += count - -metal4_nofill = get_polygons(50, 23) -metal4_nofill = metal4_nofill.not(nodrc_drw) -count = metal4_nofill.count() -logger.info("metal4_nofill has #{count} polygons") -polygons_count += count - -metal4_text = labels(50, 25) -metal4_text = metal4_text.not(nodrc_drw) -count = metal4_text.count() -logger.info("metal4_text has #{count} polygons") -polygons_count += count - -metal4_OPC = get_polygons(50, 26) -metal4_OPC = metal4_OPC.not(nodrc_drw) -count = metal4_OPC.count() -logger.info("metal4_OPC has #{count} polygons") -polygons_count += count - -metal4_noqrc = get_polygons(50, 28) -metal4_noqrc = metal4_noqrc.not(nodrc_drw) -count = metal4_noqrc.count() -logger.info("metal4_noqrc has #{count} polygons") -polygons_count += count - -metal4_res = get_polygons(50, 29) -metal4_res = metal4_res.not(nodrc_drw) -count = metal4_res.count() -logger.info("metal4_res has #{count} polygons") -polygons_count += count - -metal4_iprobe = get_polygons(50, 33) -metal4_iprobe = metal4_iprobe.not(nodrc_drw) -count = metal4_iprobe.count() -logger.info("metal4_iprobe has #{count} polygons") -polygons_count += count - -metal4_diffprb = get_polygons(50, 34) -metal4_diffprb = metal4_diffprb.not(nodrc_drw) -count = metal4_diffprb.count() -logger.info("metal4_diffprb has #{count} polygons") -polygons_count += count - -heattrans_drw = get_polygons(51, 0) -heattrans_drw = heattrans_drw.not(nodrc_drw) -count = heattrans_drw.count() -logger.info("heattrans_drw has #{count} polygons") -polygons_count += count - -heatres_drw = get_polygons(52, 0) -heatres_drw = heatres_drw.not(nodrc_drw) -count = heatres_drw.count() -logger.info("heatres_drw has #{count} polygons") -polygons_count += count - -fbe_drw = get_polygons(54, 0) -fbe_drw = fbe_drw.not(nodrc_drw) -count = fbe_drw.count() -logger.info("fbe_drw has #{count} polygons") -polygons_count += count - -empoly_drw = get_polygons(55, 0) -empoly_drw = empoly_drw.not(nodrc_drw) -count = empoly_drw.count() -logger.info("empoly_drw has #{count} polygons") -polygons_count += count - -digisub_drw = get_polygons(60, 0) -digisub_drw = digisub_drw.not(nodrc_drw) -count = digisub_drw.count() -logger.info("digisub_drw has #{count} polygons") -polygons_count += count - -text_drw = labels(63, 0) -text_drw = text_drw.not(nodrc_drw) -count = text_drw.count() -logger.info("text_drw has #{count} polygons") -polygons_count += count - -via4_drw = get_polygons(66, 0) -via4_drw = via4_drw.not(nodrc_drw) -count = via4_drw.count() -logger.info("via4_drw has #{count} polygons") -polygons_count += count - -metal5_drw = get_polygons(67, 0) -metal5_drw = metal5_drw.not(nodrc_drw) -count = metal5_drw.count() -logger.info("metal5_drw has #{count} polygons") -polygons_count += count - -metal5_filler = get_polygons(67, 22) -metal5_filler = metal5_filler.not(nodrc_drw) -count = metal5_filler.count() -logger.info("metal5_filler has #{count} polygons") -polygons_count += count - -metal5_slit = get_polygons(67, 24) -metal5_slit = metal5_slit.not(nodrc_drw) -count = metal5_slit.count() -logger.info("metal5_slit has #{count} polygons") -polygons_count += count - -# metal5 org -metal5 = metal5_drw.join(metal5_filler).not(metal5_slit) -count = metal5.count() -logger.info("metal5 has #{count} polygons") - -metal5_pin = get_polygons(67, 2) -metal5_pin = metal5_pin.not(nodrc_drw) -count = metal5_pin.count() -logger.info("metal5_pin has #{count} polygons") -polygons_count += count - -metal5_mask = get_polygons(67, 20) -metal5_mask = metal5_mask.not(nodrc_drw) -count = metal5_mask.count() -logger.info("metal5_mask has #{count} polygons") -polygons_count += count - -metal5_nofill = get_polygons(67, 23) -metal5_nofill = metal5_nofill.not(nodrc_drw) -count = metal5_nofill.count() -logger.info("metal5_nofill has #{count} polygons") -polygons_count += count - -metal5_text = labels(67, 25) -metal5_text = metal5_text.not(nodrc_drw) -count = metal5_text.count() -logger.info("metal5_text has #{count} polygons") -polygons_count += count - -metal5_OPC = get_polygons(67, 26) -metal5_OPC = metal5_OPC.not(nodrc_drw) -count = metal5_OPC.count() -logger.info("metal5_OPC has #{count} polygons") -polygons_count += count - -metal5_noqrc = get_polygons(67, 28) -metal5_noqrc = metal5_noqrc.not(nodrc_drw) -count = metal5_noqrc.count() -logger.info("metal5_noqrc has #{count} polygons") -polygons_count += count - -metal5_res = get_polygons(67, 29) -metal5_res = metal5_res.not(nodrc_drw) -count = metal5_res.count() -logger.info("metal5_res has #{count} polygons") -polygons_count += count - -metal5_iprobe = get_polygons(67, 33) -metal5_iprobe = metal5_iprobe.not(nodrc_drw) -count = metal5_iprobe.count() -logger.info("metal5_iprobe has #{count} polygons") -polygons_count += count - -metal5_diffprb = get_polygons(67, 34) -metal5_diffprb = metal5_diffprb.not(nodrc_drw) -count = metal5_diffprb.count() -logger.info("metal5_diffprb has #{count} polygons") -polygons_count += count - -radhard_drw = get_polygons(68, 0) -radhard_drw = radhard_drw.not(nodrc_drw) -count = radhard_drw.count() -logger.info("radhard_drw has #{count} polygons") -polygons_count += count - -memcap_drw = get_polygons(69, 0) -memcap_drw = memcap_drw.not(nodrc_drw) -count = memcap_drw.count() -logger.info("memcap_drw has #{count} polygons") -polygons_count += count - -varicap_drw = get_polygons(70, 0) -varicap_drw = varicap_drw.not(nodrc_drw) -count = varicap_drw.count() -logger.info("varicap_drw has #{count} polygons") -polygons_count += count - -intbondvia_drw = get_polygons(72, 0) -intbondvia_drw = intbondvia_drw.not(nodrc_drw) -count = intbondvia_drw.count() -logger.info("intbondvia_drw has #{count} polygons") -polygons_count += count - -intbondmet_drw = get_polygons(73, 0) -intbondmet_drw = intbondmet_drw.not(nodrc_drw) -count = intbondmet_drw.count() -logger.info("intbondmet_drw has #{count} polygons") -polygons_count += count - -devbondvia_drw = get_polygons(74, 0) -devbondvia_drw = devbondvia_drw.not(nodrc_drw) -count = devbondvia_drw.count() -logger.info("devbondvia_drw has #{count} polygons") -polygons_count += count - -devbondmet_drw = get_polygons(75, 0) -devbondmet_drw = devbondmet_drw.not(nodrc_drw) -count = devbondmet_drw.count() -logger.info("devbondmet_drw has #{count} polygons") -polygons_count += count - -devtrench_drw = get_polygons(76, 0) -devtrench_drw = devtrench_drw.not(nodrc_drw) -count = devtrench_drw.count() -logger.info("devtrench_drw has #{count} polygons") -polygons_count += count - -redist_drw = get_polygons(77, 0) -redist_drw = redist_drw.not(nodrc_drw) -count = redist_drw.count() -logger.info("redist_drw has #{count} polygons") -polygons_count += count - -graphbot_drw = get_polygons(78, 0) -graphbot_drw = graphbot_drw.not(nodrc_drw) -count = graphbot_drw.count() -logger.info("graphbot_drw has #{count} polygons") -polygons_count += count - -graphtop_drw = get_polygons(79, 0) -graphtop_drw = graphtop_drw.not(nodrc_drw) -count = graphtop_drw.count() -logger.info("graphtop_drw has #{count} polygons") -polygons_count += count - -antvia1_drw = get_polygons(83, 0) -antvia1_drw = antvia1_drw.not(nodrc_drw) -count = antvia1_drw.count() -logger.info("antvia1_drw has #{count} polygons") -polygons_count += count - -antmetal2_drw = get_polygons(84, 0) -antmetal2_drw = antmetal2_drw.not(nodrc_drw) -count = antmetal2_drw.count() -logger.info("antmetal2_drw has #{count} polygons") -polygons_count += count - -graphcont_drw = get_polygons(85, 0) -graphcont_drw = graphcont_drw.not(nodrc_drw) -count = graphcont_drw.count() -logger.info("graphcont_drw has #{count} polygons") -polygons_count += count - -siwg_drw = get_polygons(86, 0) -siwg_drw = siwg_drw.not(nodrc_drw) -count = siwg_drw.count() -logger.info("siwg_drw has #{count} polygons") -polygons_count += count - -siwg_filler = get_polygons(86, 22) -siwg_filler = siwg_filler.not(nodrc_drw) -count = siwg_filler.count() -logger.info("siwg_filler has #{count} polygons") -polygons_count += count - -siwg_nofill = get_polygons(86, 23) -siwg_nofill = siwg_nofill.not(nodrc_drw) -count = siwg_nofill.count() -logger.info("siwg_nofill has #{count} polygons") -polygons_count += count - -sigrating_drw = get_polygons(87, 0) -sigrating_drw = sigrating_drw.not(nodrc_drw) -count = sigrating_drw.count() -logger.info("sigrating_drw has #{count} polygons") -polygons_count += count - -singrating_drw = get_polygons(88, 0) -singrating_drw = singrating_drw.not(nodrc_drw) -count = singrating_drw.count() -logger.info("singrating_drw has #{count} polygons") -polygons_count += count - -graphpas_drw = get_polygons(89, 0) -graphpas_drw = graphpas_drw.not(nodrc_drw) -count = graphpas_drw.count() -logger.info("graphpas_drw has #{count} polygons") -polygons_count += count - -emwind3_drw = get_polygons(90, 0) -emwind3_drw = emwind3_drw.not(nodrc_drw) -count = emwind3_drw.count() -logger.info("emwind3_drw has #{count} polygons") -polygons_count += count - -emwihv3_drw = get_polygons(91, 0) -emwihv3_drw = emwihv3_drw.not(nodrc_drw) -count = emwihv3_drw.count() -logger.info("emwihv3_drw has #{count} polygons") -polygons_count += count - -redbulay_drw = get_polygons(92, 0) -redbulay_drw = redbulay_drw.not(nodrc_drw) -count = redbulay_drw.count() -logger.info("redbulay_drw has #{count} polygons") -polygons_count += count - -smos_drw = get_polygons(93, 0) -smos_drw = smos_drw.not(nodrc_drw) -count = smos_drw.count() -logger.info("smos_drw has #{count} polygons") -polygons_count += count - -graphpad_drw = get_polygons(97, 0) -graphpad_drw = graphpad_drw.not(nodrc_drw) -count = graphpad_drw.count() -logger.info("graphpad_drw has #{count} polygons") -polygons_count += count - -polimide_drw = get_polygons(98, 0) -polimide_drw = polimide_drw.not(nodrc_drw) -count = polimide_drw.count() -logger.info("polimide_drw has #{count} polygons") -polygons_count += count - -polimide_pin = get_polygons(98, 2) -polimide_pin = polimide_pin.not(nodrc_drw) -count = polimide_pin.count() -logger.info("polimide_pin has #{count} polygons") -polygons_count += count - -recog_drw = get_polygons(99, 0) -recog_drw = recog_drw.not(nodrc_drw) -count = recog_drw.count() -logger.info("recog_drw has #{count} polygons") -polygons_count += count - -recog_pin = get_polygons(99, 2) -recog_pin = recog_pin.not(nodrc_drw) -count = recog_pin.count() -logger.info("recog_pin has #{count} polygons") -polygons_count += count - -recog_esd = get_polygons(99, 30) -recog_esd = recog_esd.not(nodrc_drw) -count = recog_esd.count() -logger.info("recog_esd has #{count} polygons") -polygons_count += count - -recog_diode = get_polygons(99, 31) -recog_diode = recog_diode.not(nodrc_drw) -count = recog_diode.count() -logger.info("recog_diode has #{count} polygons") -polygons_count += count - -recog_tsv = get_polygons(99, 32) -recog_tsv = recog_tsv.not(nodrc_drw) -count = recog_tsv.count() -logger.info("recog_tsv has #{count} polygons") -polygons_count += count - -recog_iprobe = get_polygons(99, 33) -recog_iprobe = recog_iprobe.not(nodrc_drw) -count = recog_iprobe.count() -logger.info("recog_iprobe has #{count} polygons") -polygons_count += count - -recog_diffprb = get_polygons(99, 34) -recog_diffprb = recog_diffprb.not(nodrc_drw) -count = recog_diffprb.count() -logger.info("recog_diffprb has #{count} polygons") -polygons_count += count - -recog_pillar = get_polygons(99, 35) -recog_pillar = recog_pillar.not(nodrc_drw) -count = recog_pillar.count() -logger.info("recog_pillar has #{count} polygons") -polygons_count += count - -recog_sbump = get_polygons(99, 36) -recog_sbump = recog_sbump.not(nodrc_drw) -count = recog_sbump.count() -logger.info("recog_sbump has #{count} polygons") -polygons_count += count - -recog_otp = get_polygons(99, 37) -recog_otp = recog_otp.not(nodrc_drw) -count = recog_otp.count() -logger.info("recog_otp has #{count} polygons") -polygons_count += count - -recog_pdiode = get_polygons(99, 38) -recog_pdiode = recog_pdiode.not(nodrc_drw) -count = recog_pdiode.count() -logger.info("recog_pdiode has #{count} polygons") -polygons_count += count - -recog_mom = get_polygons(99, 39) -recog_mom = recog_mom.not(nodrc_drw) -count = recog_mom.count() -logger.info("recog_mom has #{count} polygons") -polygons_count += count - -recog_pcm = get_polygons(99, 100) -recog_pcm = recog_pcm.not(nodrc_drw) -count = recog_pcm.count() -logger.info("recog_pcm has #{count} polygons") -polygons_count += count - -colopen_drw = get_polygons(101, 0) -colopen_drw = colopen_drw.not(nodrc_drw) -count = colopen_drw.count() -logger.info("colopen_drw has #{count} polygons") -polygons_count += count - -graphmetal1_drw = get_polygons(109, 0) -graphmetal1_drw = graphmetal1_drw.not(nodrc_drw) -count = graphmetal1_drw.count() -logger.info("graphmetal1_drw has #{count} polygons") -polygons_count += count - -graphmetal1_filler = get_polygons(109, 22) -graphmetal1_filler = graphmetal1_filler.not(nodrc_drw) -count = graphmetal1_filler.count() -logger.info("graphmetal1_filler has #{count} polygons") -polygons_count += count - -graphmetal1_nofill = get_polygons(109, 23) -graphmetal1_nofill = graphmetal1_nofill.not(nodrc_drw) -count = graphmetal1_nofill.count() -logger.info("graphmetal1_nofill has #{count} polygons") -polygons_count += count - -graphmetal1_slit = get_polygons(109, 24) -graphmetal1_slit = graphmetal1_slit.not(nodrc_drw) -count = graphmetal1_slit.count() -logger.info("graphmetal1_slit has #{count} polygons") -polygons_count += count - -graphmetal1_OPC = get_polygons(109, 26) -graphmetal1_OPC = graphmetal1_OPC.not(nodrc_drw) -count = graphmetal1_OPC.count() -logger.info("graphmetal1_OPC has #{count} polygons") -polygons_count += count - -graphmet1l_drw = get_polygons(110, 0) -graphmet1l_drw = graphmet1l_drw.not(nodrc_drw) -count = graphmet1l_drw.count() -logger.info("graphmet1l_drw has #{count} polygons") -polygons_count += count - -graphmet1l_filler = get_polygons(110, 22) -graphmet1l_filler = graphmet1l_filler.not(nodrc_drw) -count = graphmet1l_filler.count() -logger.info("graphmet1l_filler has #{count} polygons") -polygons_count += count - -graphmet1l_nofill = get_polygons(110, 23) -graphmet1l_nofill = graphmet1l_nofill.not(nodrc_drw) -count = graphmet1l_nofill.count() -logger.info("graphmet1l_nofill has #{count} polygons") -polygons_count += count - -graphmet1l_slit = get_polygons(110, 24) -graphmet1l_slit = graphmet1l_slit.not(nodrc_drw) -count = graphmet1l_slit.count() -logger.info("graphmet1l_slit has #{count} polygons") -polygons_count += count - -graphmet1l_OPC = get_polygons(110, 26) -graphmet1l_OPC = graphmet1l_OPC.not(nodrc_drw) -count = graphmet1l_OPC.count() -logger.info("graphmet1l_OPC has #{count} polygons") -polygons_count += count - -extblock_drw = get_polygons(111, 0) -extblock_drw = extblock_drw.not(nodrc_drw) -count = extblock_drw.count() -logger.info("extblock_drw has #{count} polygons") -polygons_count += count - -nldd_drw = get_polygons(112, 0) -nldd_drw = nldd_drw.not(nodrc_drw) -count = nldd_drw.count() -logger.info("nldd_drw has #{count} polygons") -polygons_count += count - -pldd_drw = get_polygons(113, 0) -pldd_drw = pldd_drw.not(nodrc_drw) -count = pldd_drw.count() -logger.info("pldd_drw has #{count} polygons") -polygons_count += count - -next_drw = get_polygons(114, 0) -next_drw = next_drw.not(nodrc_drw) -count = next_drw.count() -logger.info("next_drw has #{count} polygons") -polygons_count += count - -pext_drw = get_polygons(115, 0) -pext_drw = pext_drw.not(nodrc_drw) -count = pext_drw.count() -logger.info("pext_drw has #{count} polygons") -polygons_count += count - -nexthv_drw = get_polygons(116, 0) -nexthv_drw = nexthv_drw.not(nodrc_drw) -count = nexthv_drw.count() -logger.info("nexthv_drw has #{count} polygons") -polygons_count += count - -pexthv_drw = get_polygons(117, 0) -pexthv_drw = pexthv_drw.not(nodrc_drw) -count = pexthv_drw.count() -logger.info("pexthv_drw has #{count} polygons") -polygons_count += count - -graphgate_drw = get_polygons(118, 0) -graphgate_drw = graphgate_drw.not(nodrc_drw) -count = graphgate_drw.count() -logger.info("graphgate_drw has #{count} polygons") -polygons_count += count - -sinwg_drw = get_polygons(119, 0) -sinwg_drw = sinwg_drw.not(nodrc_drw) -count = sinwg_drw.count() -logger.info("sinwg_drw has #{count} polygons") -polygons_count += count - -sinwg_filler = get_polygons(119, 22) -sinwg_filler = sinwg_filler.not(nodrc_drw) -count = sinwg_filler.count() -logger.info("sinwg_filler has #{count} polygons") -polygons_count += count - -sinwg_nofill = get_polygons(119, 23) -sinwg_nofill = sinwg_nofill.not(nodrc_drw) -count = sinwg_nofill.count() -logger.info("sinwg_nofill has #{count} polygons") -polygons_count += count - -mempad_drw = get_polygons(124, 0) -mempad_drw = mempad_drw.not(nodrc_drw) -count = mempad_drw.count() -logger.info("mempad_drw has #{count} polygons") -polygons_count += count - -topvia1_drw = get_polygons(125, 0) -topvia1_drw = topvia1_drw.not(nodrc_drw) -count = topvia1_drw.count() -logger.info("topvia1_drw has #{count} polygons") -polygons_count += count - -topmetal1_drw = get_polygons(126, 0) -topmetal1_drw = topmetal1_drw.not(nodrc_drw) -count = topmetal1_drw.count() -logger.info("topmetal1_drw has #{count} polygons") -polygons_count += count - -topmetal1_filler = get_polygons(126, 22) -topmetal1_filler = topmetal1_filler.not(nodrc_drw) -count = topmetal1_filler.count() -logger.info("topmetal1_filler has #{count} polygons") -polygons_count += count - -topmetal1_slit = get_polygons(126, 24) -topmetal1_slit = topmetal1_slit.not(nodrc_drw) -count = topmetal1_slit.count() -logger.info("topmetal1_slit has #{count} polygons") -polygons_count += count - -# topmetal1 org -topmetal1 = topmetal1_drw.join(topmetal1_filler).not(topmetal1_slit) -count = topmetal1.count() -logger.info("topmetal1 has #{count} polygons") - -topmetal1_pin = get_polygons(126, 2) -topmetal1_pin = topmetal1_pin.not(nodrc_drw) -count = topmetal1_pin.count() -logger.info("topmetal1_pin has #{count} polygons") -polygons_count += count - -topmetal1_mask = get_polygons(126, 20) -topmetal1_mask = topmetal1_mask.not(nodrc_drw) -count = topmetal1_mask.count() -logger.info("topmetal1_mask has #{count} polygons") -polygons_count += count - -topmetal1_nofill = get_polygons(126, 23) -topmetal1_nofill = topmetal1_nofill.not(nodrc_drw) -count = topmetal1_nofill.count() -logger.info("topmetal1_nofill has #{count} polygons") -polygons_count += count - -topmetal1_text = labels(126, 25) -topmetal1_text = topmetal1_text.not(nodrc_drw) -count = topmetal1_text.count() -logger.info("topmetal1_text has #{count} polygons") -polygons_count += count - -topmetal1_noqrc = get_polygons(126, 28) -topmetal1_noqrc = topmetal1_noqrc.not(nodrc_drw) -count = topmetal1_noqrc.count() -logger.info("topmetal1_noqrc has #{count} polygons") -polygons_count += count - -topmetal1_res = get_polygons(126, 29) -topmetal1_res = topmetal1_res.not(nodrc_drw) -count = topmetal1_res.count() -logger.info("topmetal1_res has #{count} polygons") -polygons_count += count - -topmetal1_iprobe = get_polygons(126, 33) -topmetal1_iprobe = topmetal1_iprobe.not(nodrc_drw) -count = topmetal1_iprobe.count() -logger.info("topmetal1_iprobe has #{count} polygons") -polygons_count += count - -topmetal1_diffprb = get_polygons(126, 34) -topmetal1_diffprb = topmetal1_diffprb.not(nodrc_drw) -count = topmetal1_diffprb.count() -logger.info("topmetal1_diffprb has #{count} polygons") -polygons_count += count - -inldpwl_drw = get_polygons(127, 0) -inldpwl_drw = inldpwl_drw.not(nodrc_drw) -count = inldpwl_drw.count() -logger.info("inldpwl_drw has #{count} polygons") -polygons_count += count - -polyres_drw = get_polygons(128, 0) -polyres_drw = polyres_drw.not(nodrc_drw) -count = polyres_drw.count() -logger.info("polyres_drw has #{count} polygons") -polygons_count += count - -polyres_pin = get_polygons(128, 2) -polyres_pin = polyres_pin.not(nodrc_drw) -count = polyres_pin.count() -logger.info("polyres_pin has #{count} polygons") -polygons_count += count - -vmim_drw = get_polygons(129, 0) -vmim_drw = vmim_drw.not(nodrc_drw) -count = vmim_drw.count() -logger.info("vmim_drw has #{count} polygons") -polygons_count += count - -nbulaycut_drw = get_polygons(131, 0) -nbulaycut_drw = nbulaycut_drw.not(nodrc_drw) -count = nbulaycut_drw.count() -logger.info("nbulaycut_drw has #{count} polygons") -polygons_count += count - -antmetal1_drw = get_polygons(132, 0) -antmetal1_drw = antmetal1_drw.not(nodrc_drw) -count = antmetal1_drw.count() -logger.info("antmetal1_drw has #{count} polygons") -polygons_count += count - -topvia2_drw = get_polygons(133, 0) -topvia2_drw = topvia2_drw.not(nodrc_drw) -count = topvia2_drw.count() -logger.info("topvia2_drw has #{count} polygons") -polygons_count += count - -topmetal2_drw = get_polygons(134, 0) -topmetal2_drw = topmetal2_drw.not(nodrc_drw) -count = topmetal2_drw.count() -logger.info("topmetal2_drw has #{count} polygons") -polygons_count += count - -topmetal2_filler = get_polygons(134, 22) -topmetal2_filler = topmetal2_filler.not(nodrc_drw) -count = topmetal2_filler.count() -logger.info("topmetal2_filler has #{count} polygons") -polygons_count += count - -topmetal2_slit = get_polygons(134, 24) -topmetal2_slit = topmetal2_slit.not(nodrc_drw) -count = topmetal2_slit.count() -logger.info("topmetal2_slit has #{count} polygons") -polygons_count += count - -# topmetal2 org -topmetal2 = topmetal2_drw.join(topmetal2_filler).not(topmetal2_slit) -count = topmetal2.count() -logger.info("topmetal2 has #{count} polygons") - -topmetal2_pin = get_polygons(134, 2) -topmetal2_pin = topmetal2_pin.not(nodrc_drw) -count = topmetal2_pin.count() -logger.info("topmetal2_pin has #{count} polygons") -polygons_count += count - -topmetal2_mask = get_polygons(134, 20) -topmetal2_mask = topmetal2_mask.not(nodrc_drw) -count = topmetal2_mask.count() -logger.info("topmetal2_mask has #{count} polygons") -polygons_count += count - -topmetal2_nofill = get_polygons(134, 23) -topmetal2_nofill = topmetal2_nofill.not(nodrc_drw) -count = topmetal2_nofill.count() -logger.info("topmetal2_nofill has #{count} polygons") -polygons_count += count - -topmetal2_text = labels(134, 25) -topmetal2_text = topmetal2_text.not(nodrc_drw) -count = topmetal2_text.count() -logger.info("topmetal2_text has #{count} polygons") -polygons_count += count - -topmetal2_noqrc = get_polygons(134, 28) -topmetal2_noqrc = topmetal2_noqrc.not(nodrc_drw) -count = topmetal2_noqrc.count() -logger.info("topmetal2_noqrc has #{count} polygons") -polygons_count += count - -topmetal2_res = get_polygons(134, 29) -topmetal2_res = topmetal2_res.not(nodrc_drw) -count = topmetal2_res.count() -logger.info("topmetal2_res has #{count} polygons") -polygons_count += count - -topmetal2_iprobe = get_polygons(134, 33) -topmetal2_iprobe = topmetal2_iprobe.not(nodrc_drw) -count = topmetal2_iprobe.count() -logger.info("topmetal2_iprobe has #{count} polygons") -polygons_count += count - -topmetal2_diffprb = get_polygons(134, 34) -topmetal2_diffprb = topmetal2_diffprb.not(nodrc_drw) -count = topmetal2_diffprb.count() -logger.info("topmetal2_diffprb has #{count} polygons") -polygons_count += count - -snsring_drw = get_polygons(135, 0) -snsring_drw = snsring_drw.not(nodrc_drw) -count = snsring_drw.count() -logger.info("snsring_drw has #{count} polygons") -polygons_count += count - -sensor_drw = get_polygons(136, 0) -sensor_drw = sensor_drw.not(nodrc_drw) -count = sensor_drw.count() -logger.info("sensor_drw has #{count} polygons") -polygons_count += count - -snsarms_drw = get_polygons(137, 0) -snsarms_drw = snsarms_drw.not(nodrc_drw) -count = snsarms_drw.count() -logger.info("snsarms_drw has #{count} polygons") -polygons_count += count - -snscmosvia_drw = get_polygons(138, 0) -snscmosvia_drw = snscmosvia_drw.not(nodrc_drw) -count = snscmosvia_drw.count() -logger.info("snscmosvia_drw has #{count} polygons") -polygons_count += count - -colwind_drw = get_polygons(139, 0) -colwind_drw = colwind_drw.not(nodrc_drw) -count = colwind_drw.count() -logger.info("colwind_drw has #{count} polygons") -polygons_count += count - -flm_drw = get_polygons(142, 0) -flm_drw = flm_drw.not(nodrc_drw) -count = flm_drw.count() -logger.info("flm_drw has #{count} polygons") -polygons_count += count - -hafniumox_drw = get_polygons(143, 0) -hafniumox_drw = hafniumox_drw.not(nodrc_drw) -count = hafniumox_drw.count() -logger.info("hafniumox_drw has #{count} polygons") -polygons_count += count - -memvia_drw = get_polygons(145, 0) -memvia_drw = memvia_drw.not(nodrc_drw) -count = memvia_drw.count() -logger.info("memvia_drw has #{count} polygons") -polygons_count += count - -thinfilmres_drw = get_polygons(146, 0) -thinfilmres_drw = thinfilmres_drw.not(nodrc_drw) -count = thinfilmres_drw.count() -logger.info("thinfilmres_drw has #{count} polygons") -polygons_count += count - -rfmem_drw = get_polygons(147, 0) -rfmem_drw = rfmem_drw.not(nodrc_drw) -count = rfmem_drw.count() -logger.info("rfmem_drw has #{count} polygons") -polygons_count += count - -norcx_drw = get_polygons(148, 0) -norcx_drw = norcx_drw.not(nodrc_drw) -count = norcx_drw.count() -logger.info("norcx_drw has #{count} polygons") -polygons_count += count - -norcx_m2m3 = get_polygons(148, 41) -norcx_m2m3 = norcx_m2m3.not(nodrc_drw) -count = norcx_m2m3.count() -logger.info("norcx_m2m3 has #{count} polygons") -polygons_count += count - -norcx_m2m4 = get_polygons(148, 42) -norcx_m2m4 = norcx_m2m4.not(nodrc_drw) -count = norcx_m2m4.count() -logger.info("norcx_m2m4 has #{count} polygons") -polygons_count += count - -norcx_m2m5 = get_polygons(148, 43) -norcx_m2m5 = norcx_m2m5.not(nodrc_drw) -count = norcx_m2m5.count() -logger.info("norcx_m2m5 has #{count} polygons") -polygons_count += count - -norcx_m2tm1 = get_polygons(148, 44) -norcx_m2tm1 = norcx_m2tm1.not(nodrc_drw) -count = norcx_m2tm1.count() -logger.info("norcx_m2tm1 has #{count} polygons") -polygons_count += count - -norcx_m2tm2 = get_polygons(148, 45) -norcx_m2tm2 = norcx_m2tm2.not(nodrc_drw) -count = norcx_m2tm2.count() -logger.info("norcx_m2tm2 has #{count} polygons") -polygons_count += count - -norcx_m3m4 = get_polygons(148, 46) -norcx_m3m4 = norcx_m3m4.not(nodrc_drw) -count = norcx_m3m4.count() -logger.info("norcx_m3m4 has #{count} polygons") -polygons_count += count - -norcx_m3m5 = get_polygons(148, 47) -norcx_m3m5 = norcx_m3m5.not(nodrc_drw) -count = norcx_m3m5.count() -logger.info("norcx_m3m5 has #{count} polygons") -polygons_count += count - -norcx_m3tm1 = get_polygons(148, 48) -norcx_m3tm1 = norcx_m3tm1.not(nodrc_drw) -count = norcx_m3tm1.count() -logger.info("norcx_m3tm1 has #{count} polygons") -polygons_count += count - -norcx_m3tm2 = get_polygons(148, 49) -norcx_m3tm2 = norcx_m3tm2.not(nodrc_drw) -count = norcx_m3tm2.count() -logger.info("norcx_m3tm2 has #{count} polygons") -polygons_count += count - -norcx_m4m5 = get_polygons(148, 50) -norcx_m4m5 = norcx_m4m5.not(nodrc_drw) -count = norcx_m4m5.count() -logger.info("norcx_m4m5 has #{count} polygons") -polygons_count += count - -norcx_m4tm1 = get_polygons(148, 51) -norcx_m4tm1 = norcx_m4tm1.not(nodrc_drw) -count = norcx_m4tm1.count() -logger.info("norcx_m4tm1 has #{count} polygons") -polygons_count += count - -norcx_m4tm2 = get_polygons(148, 52) -norcx_m4tm2 = norcx_m4tm2.not(nodrc_drw) -count = norcx_m4tm2.count() -logger.info("norcx_m4tm2 has #{count} polygons") -polygons_count += count - -norcx_m5tm1 = get_polygons(148, 53) -norcx_m5tm1 = norcx_m5tm1.not(nodrc_drw) -count = norcx_m5tm1.count() -logger.info("norcx_m5tm1 has #{count} polygons") -polygons_count += count - -norcx_m5tm2 = get_polygons(148, 54) -norcx_m5tm2 = norcx_m5tm2.not(nodrc_drw) -count = norcx_m5tm2.count() -logger.info("norcx_m5tm2 has #{count} polygons") -polygons_count += count - -norcx_tm1tm2 = get_polygons(148, 55) -norcx_tm1tm2 = norcx_tm1tm2.not(nodrc_drw) -count = norcx_tm1tm2.count() -logger.info("norcx_tm1tm2 has #{count} polygons") -polygons_count += count - -norcx_m1sub = get_polygons(148, 123) -norcx_m1sub = norcx_m1sub.not(nodrc_drw) -count = norcx_m1sub.count() -logger.info("norcx_m1sub has #{count} polygons") -polygons_count += count - -norcx_m2sub = get_polygons(148, 124) -norcx_m2sub = norcx_m2sub.not(nodrc_drw) -count = norcx_m2sub.count() -logger.info("norcx_m2sub has #{count} polygons") -polygons_count += count - -norcx_m3sub = get_polygons(148, 125) -norcx_m3sub = norcx_m3sub.not(nodrc_drw) -count = norcx_m3sub.count() -logger.info("norcx_m3sub has #{count} polygons") -polygons_count += count - -norcx_m4sub = get_polygons(148, 126) -norcx_m4sub = norcx_m4sub.not(nodrc_drw) -count = norcx_m4sub.count() -logger.info("norcx_m4sub has #{count} polygons") -polygons_count += count - -norcx_m5sub = get_polygons(148, 127) -norcx_m5sub = norcx_m5sub.not(nodrc_drw) -count = norcx_m5sub.count() -logger.info("norcx_m5sub has #{count} polygons") -polygons_count += count - -norcx_tm1sub = get_polygons(148, 300) -norcx_tm1sub = norcx_tm1sub.not(nodrc_drw) -count = norcx_tm1sub.count() -logger.info("norcx_tm1sub has #{count} polygons") -polygons_count += count - -norcx_tm2sub = get_polygons(148, 301) -norcx_tm2sub = norcx_tm2sub.not(nodrc_drw) -count = norcx_tm2sub.count() -logger.info("norcx_tm2sub has #{count} polygons") -polygons_count += count - -snsbotvia_drw = get_polygons(149, 0) -snsbotvia_drw = snsbotvia_drw.not(nodrc_drw) -count = snsbotvia_drw.count() -logger.info("snsbotvia_drw has #{count} polygons") -polygons_count += count - -snstopvia_drw = get_polygons(151, 0) -snstopvia_drw = snstopvia_drw.not(nodrc_drw) -count = snstopvia_drw.count() -logger.info("snstopvia_drw has #{count} polygons") -polygons_count += count - -deepvia_drw = get_polygons(152, 0) -deepvia_drw = deepvia_drw.not(nodrc_drw) -count = deepvia_drw.count() -logger.info("deepvia_drw has #{count} polygons") -polygons_count += count - -fgetch_drw = get_polygons(153, 0) -fgetch_drw = fgetch_drw.not(nodrc_drw) -count = fgetch_drw.count() -logger.info("fgetch_drw has #{count} polygons") -polygons_count += count - -ctrgat_drw = get_polygons(154, 0) -ctrgat_drw = ctrgat_drw.not(nodrc_drw) -count = ctrgat_drw.count() -logger.info("ctrgat_drw has #{count} polygons") -polygons_count += count - -fgimp_drw = get_polygons(155, 0) -fgimp_drw = fgimp_drw.not(nodrc_drw) -count = fgimp_drw.count() -logger.info("fgimp_drw has #{count} polygons") -polygons_count += count - -emwihv_drw = get_polygons(156, 0) -emwihv_drw = emwihv_drw.not(nodrc_drw) -count = emwihv_drw.count() -logger.info("emwihv_drw has #{count} polygons") -polygons_count += count - -lbe_drw = get_polygons(157, 0) -lbe_drw = lbe_drw.not(nodrc_drw) -count = lbe_drw.count() -logger.info("lbe_drw has #{count} polygons") -polygons_count += count - -alcustop_drw = get_polygons(159, 0) -alcustop_drw = alcustop_drw.not(nodrc_drw) -count = alcustop_drw.count() -logger.info("alcustop_drw has #{count} polygons") -polygons_count += count - -nometfiller_drw = get_polygons(160, 0) -nometfiller_drw = nometfiller_drw.not(nodrc_drw) -count = nometfiller_drw.count() -logger.info("nometfiller_drw has #{count} polygons") -polygons_count += count - -prboundary_drw = get_polygons(189, 0) -prboundary_drw = prboundary_drw.not(nodrc_drw) -count = prboundary_drw.count() -logger.info("prboundary_drw has #{count} polygons") -polygons_count += count - -exchange0_drw = get_polygons(190, 0) -exchange0_drw = exchange0_drw.not(nodrc_drw) -count = exchange0_drw.count() -logger.info("exchange0_drw has #{count} polygons") -polygons_count += count - -exchange0_pin = get_polygons(190, 2) -exchange0_pin = exchange0_pin.not(nodrc_drw) -count = exchange0_pin.count() -logger.info("exchange0_pin has #{count} polygons") -polygons_count += count - -exchange0_text = labels(190, 25) -exchange0_text = exchange0_text.not(nodrc_drw) -count = exchange0_text.count() -logger.info("exchange0_text has #{count} polygons") -polygons_count += count - -exchange1_drw = get_polygons(191, 0) -exchange1_drw = exchange1_drw.not(nodrc_drw) -count = exchange1_drw.count() -logger.info("exchange1_drw has #{count} polygons") -polygons_count += count - -exchange1_pin = get_polygons(191, 2) -exchange1_pin = exchange1_pin.not(nodrc_drw) -count = exchange1_pin.count() -logger.info("exchange1_pin has #{count} polygons") -polygons_count += count - -exchange1_text = labels(191, 25) -exchange1_text = exchange1_text.not(nodrc_drw) -count = exchange1_text.count() -logger.info("exchange1_text has #{count} polygons") -polygons_count += count - -exchange2_drw = get_polygons(192, 0) -exchange2_drw = exchange2_drw.not(nodrc_drw) -count = exchange2_drw.count() -logger.info("exchange2_drw has #{count} polygons") -polygons_count += count - -exchange2_pin = get_polygons(192, 2) -exchange2_pin = exchange2_pin.not(nodrc_drw) -count = exchange2_pin.count() -logger.info("exchange2_pin has #{count} polygons") -polygons_count += count - -exchange2_text = labels(192, 25) -exchange2_text = exchange2_text.not(nodrc_drw) -count = exchange2_text.count() -logger.info("exchange2_text has #{count} polygons") -polygons_count += count - -exchange3_drw = get_polygons(193, 0) -exchange3_drw = exchange3_drw.not(nodrc_drw) -count = exchange3_drw.count() -logger.info("exchange3_drw has #{count} polygons") -polygons_count += count - -exchange3_pin = get_polygons(193, 2) -exchange3_pin = exchange3_pin.not(nodrc_drw) -count = exchange3_pin.count() -logger.info("exchange3_pin has #{count} polygons") -polygons_count += count - -exchange3_text = labels(193, 25) -exchange3_text = exchange3_text.not(nodrc_drw) -count = exchange3_text.count() -logger.info("exchange3_text has #{count} polygons") -polygons_count += count - -exchange4_drw = get_polygons(194, 0) -exchange4_drw = exchange4_drw.not(nodrc_drw) -count = exchange4_drw.count() -logger.info("exchange4_drw has #{count} polygons") -polygons_count += count - -exchange4_pin = get_polygons(194, 2) -exchange4_pin = exchange4_pin.not(nodrc_drw) -count = exchange4_pin.count() -logger.info("exchange4_pin has #{count} polygons") -polygons_count += count - -exchange4_text = labels(194, 25) -exchange4_text = exchange4_text.not(nodrc_drw) -count = exchange4_text.count() -logger.info("exchange4_text has #{count} polygons") -polygons_count += count - -isonwell_drw = get_polygons(257, 0) -isonwell_drw = isonwell_drw.not(nodrc_drw) -count = isonwell_drw.count() -logger.info("isonwell_drw has #{count} polygons") -polygons_count += count - - -logger.info("Total no. of polygons in the design is #{polygons_count}") diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_connections.lvs deleted file mode 100644 index ca676a72d8..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_connections.lvs +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ----- MOSFET CONNECTIONS ------ -#================================ - -logger.info('Starting LVS MOSFET CONNECTIONS') diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_derivations.lvs deleted file mode 100644 index 9dead314d7..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_derivations.lvs +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================== -# ------ MOSFET DERIVATIONS ------- -#================================== - -logger.info('Starting MOSFET DERIVATIONS') - -mos_exclude = pwell_block.join(nsd_drw).join(trans_drw) - .join(emwind_drw).join(emwihv_drw).join(salblock_drw) - .join(polyres_drw).join(extblock_drw).join(res_drw) - .join(activ_mask).join(recog_diode).join(recog_esd) - .join(ind_drw).join(ind_pin).join(ind_drw) - .join(substrate_drw).join(nsd_block) - -# ==== General FETs & RF-FETs ===== - -rfnmos_exc = nwell_drw.join(psd_drw).join(mos_exclude) -rfpmos_exc = pwell.join(nwell_holes).join(mos_exclude) - -# Get case insensitive patterns for FETs -rfnmos_patt = glob_to_case_insensitive_glob("rfnmos") -rfnmoshv_patt = glob_to_case_insensitive_glob("rfnmosHV") - -rfpmos_patt = glob_to_case_insensitive_glob("rfpmos") -rfpmoshv_patt = glob_to_case_insensitive_glob("rfpmosHV") - -rfnmos_mk_gen = ptap.join(ptap_holes) -rfnmos_mk = rfnmos_mk_gen.interacting(text_drw.texts(rfnmos_patt)) -rfnmoshv_mk = rfnmos_mk_gen.interacting(text_drw.texts(rfnmoshv_patt)) - -rfpmos_mk_gen = ntap.join(ntap_holes) -rfpmos_mk = rfpmos_mk_gen.interacting(text_drw.texts(rfpmos_patt)) -rfpmoshv_mk = rfpmos_mk_gen.interacting(text_drw.texts(rfpmoshv_patt)) - -# ============== -# ---- NMOS ---- -# ============== - -logger.info('Starting NMOS DERIVATIONS') - -# for regular FETs -nmos_exc = rfnmos_exc.join(rfnmos_mk) -nmoshv_exc = rfnmos_exc.join(rfnmoshv_mk) - -# nmos - LV -ngate_lv = ngate_lv_base.not(nmos_exc) - -# nmos - HV -ngate_hv = ngate_hv_base.not(nmoshv_exc) - -# ============== -# ---- PMOS ---- -# ============== - -logger.info('Starting PMOS DERIVATIONS') - -# for regular FETs -pmos_exc = rfpmos_exc.join(rfpmos_mk) -pmoshv_exc = rfpmos_exc.join(rfpmoshv_mk) - -# pmos - LV -pgate_lv = pgate_lv_base.not(pmos_exc) - -# pmos - HV -pgate_hv = pgate_hv_base.not(pmoshv_exc) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_extraction.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_extraction.lvs deleted file mode 100644 index a6275d1052..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/mos_extraction.lvs +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -# =============================== -# ------ MOSFET EXTRACTION ------ -# =============================== - -logger.info('Starting MOSFET EXTRACTION') - -# ============== -# ---- NMOS ---- -# ============== - - -logger.info('Starting NMOS EXTRACTION') - -# nmos - LV -logger.info('Extraction of NMOS-LV transistor') -extract_devices(mos4('sg13_lv_nmos'), - { 'SD' => nsd_fet, - 'G' => ngate_lv, - 'tS' => nsd_fet, - 'tD' => nsd_fet, - 'tG' => poly_con, - 'W' => pwell }) - -# nmos - HV -logger.info('Extraction of NMOS-HV transistor') -extract_devices(mos4('sg13_hv_nmos'), - { 'SD' => nsd_fet, - 'G' => ngate_hv, - 'tS' => nsd_fet, - 'tD' => nsd_fet, - 'tG' => poly_con, - 'W' => pwell }) - -# ============== -# ---- PMOS ---- -# ============== - -logger.info('Starting PMOS EXTRACTION') - -# pmos - LV -logger.info('Extraction of PMOS-LV transistor') -extract_devices(mos4('sg13_lv_pmos'), - { 'SD' => psd_fet, - 'G' => pgate_lv, - 'tS' => psd_fet, - 'tD' => psd_fet, - 'tG' => poly_con, - 'W' => nwell_drw }) - -# pmos - HV -logger.info('Extraction of PMOS-HV transistor') -extract_devices(mos4('sg13_hv_pmos'), - { 'SD' => psd_fet, - 'G' => pgate_hv, - 'tS' => psd_fet, - 'tD' => psd_fet, - 'tG' => poly_con, - 'W' => nwell_drw }) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/res_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/res_connections.lvs deleted file mode 100644 index c3ec332e8c..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/res_connections.lvs +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ---- RESISTOR CONNECTIONS ----- -#================================ - -logger.info('Starting RESISTOR CONNECTIONS') - -# === rsil === -connect(rsil_ports, cont_drw) - -# === rppd === -connect(rppd_ports, cont_drw) - -# === rhigh === -connect(rhigh_ports, cont_drw) - -# === Metal Res === -# Added in general connections diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/res_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/res_derivations.lvs deleted file mode 100644 index cb85635fe4..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/res_derivations.lvs +++ /dev/null @@ -1,75 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ---- RESISTOR DERIVATIONS ----- -#================================ - -logger.info('Starting RESISTOR DERIVATIONS') - -polyres_exclude = activ.join(pwell_block).join(nsd_block) - .join(nbulay_drw).join(thickgateox_drw).join(trans_drw) - .join(emwind_drw).join(emwihv_drw).join(activ_mask) - .join(recog_diode).join(recog_esd).join(ind_drw) - .join(ind_pin).join(substrate_drw) - -# ============== -# ---- POLY ---- -# ============== - -## polyres -polyres_mk = polyres_drw.and(extblock_drw).interacting(gatpoly).not(polyres_exclude) - -## rhigh -rhigh_res = polyres_mk.and(psd_drw).and(nsd_drw).and(salblock_drw) -rhigh_ports = gatpoly.interacting(rhigh_res).not(rhigh_res) - -## rppd -rppd_res = polyres_mk.and(psd_drw).and(salblock_drw).not(nsd_block).not(nsd_drw) -rppd_ports = gatpoly.interacting(rppd_res).not(rppd_res) - -## rsil -rsil_exc = psd_drw.join(salblock_drw).join(nsd_drw).join(nsd_block) -rsil_res = polyres_mk.and(res_drw).not(rsil_exc) -rsil_ports = gatpoly.interacting(rsil_res).not(rsil_res) - -# =============== -# ---- METAL ---- -# =============== - -# res_metal1 -res_metal1 = metal1.and(metal1_res) - -# res_metal2 -res_metal2 = metal2.and(metal2_res) - -# res_metal3 -res_metal3 = metal3.and(metal3_res) - -# res_metal4 -res_metal4 = metal4.and(metal4_res) - -# res_metal5 -res_metal5 = metal5.and(metal5_res) - -# res_topmetal1 -res_topmetal1 = topmetal1.and(topmetal1_res) - -# res_topmetal2 -res_topmetal2 = topmetal2.and(topmetal2_res) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/res_extraction.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/res_extraction.lvs deleted file mode 100644 index d048391105..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/res_extraction.lvs +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ---- RESISTOR EXTRACTIONS ----- -#================================ - -logger.info('Starting RESISTOR EXTRACTION') - -# ============== -# ---- POLY ---- -# ============== - -# rsil -logger.info('Extracting rsil resistor') -extract_devices(GeneralNTerminalExtractor.new('rsil', 2), { - 'core' => rsil_res, - 'ports' => rsil_ports, - 'meas_mk' => polyres_drw, - 'dev_mk' => polyres_drw.interacting(rsil_res), - 'sub_mk' => pwell.join(nwell_drw) - }) -# rppd -logger.info('Extracting rppd resistor') -extract_devices(GeneralNTerminalExtractor.new('rppd', 2), { - 'core' => rppd_res, - 'ports' => rppd_ports, - 'meas_mk' => polyres_drw, - 'dev_mk' => polyres_drw.interacting(rppd_res), - 'sub_mk' => pwell.join(nwell_drw) - }) - -# rhigh -logger.info('Extracting rhigh resistor') -extract_devices(GeneralNTerminalExtractor.new('rhigh', 2), { - 'core' => rhigh_res, - 'ports' => rhigh_ports, - 'meas_mk' => polyres_drw, - 'dev_mk' => polyres_drw.interacting(rhigh_res), - 'sub_mk' => pwell.join(nwell_drw) - }) - -# =============== -# ---- METAL ---- -# =============== - -# RM1 -logger.info('Extracting res_metal1 resistor') -extract_devices(resistor('res_metal1', 1.0, RES2), { 'R' => res_metal1, 'C' => metal1_con }) - -# RM2 -logger.info('Extracting res_metal2 resistor') -extract_devices(resistor('res_metal2', 1.0, RES2), { 'R' => res_metal2, 'C' => metal2_con }) - -# RM3 -logger.info('Extracting res_metal3 resistor') -extract_devices(resistor('res_metal3', 1.0, RES2), { 'R' => res_metal3, 'C' => metal3_con }) - -# RM4 -logger.info('Extracting res_metal4 resistor') -extract_devices(resistor('res_metal4', 1.0, RES2), { 'R' => res_metal4, 'C' => metal4_con }) - -# RM5 -logger.info('Extracting res_metal5 resistor') -extract_devices(resistor('res_metal5', 1.0, RES2), { 'R' => res_metal5, 'C' => metal5_con }) - -# RTM1 -logger.info('Extracting res_topmetal1 resistor') -extract_devices(resistor('res_topmetal1', 1.0, RES2), { 'R' => res_topmetal1, 'C' => topmetal1_con }) - -# RTM2 -logger.info('Extracting res_topmetal2 resistor') -extract_devices(resistor('res_topmetal2', 1.0, RES2), { 'R' => res_topmetal2, 'C' => topmetal2_con }) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_connections.lvs deleted file mode 100644 index 2245c9e41b..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_connections.lvs +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ---- RF-MOSFET CONNECTIONS ---- -#================================ - -logger.info('Starting LVS RF-MOSFET CONNECTIONS') diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_derivations.lvs deleted file mode 100644 index 87a0f0a3f9..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_derivations.lvs +++ /dev/null @@ -1,47 +0,0 @@ -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================== -# ----- RF-MOSFET DERIVATIONS ----- -#================================== - -logger.info('Starting RF-MOSFET DERIVATIONS') - -# =============== -# --- RF-NMOS --- -# =============== - -logger.info('Starting RF-NMOS DERIVATIONS') - - -# rfnmos - LV -rfngate_lv = ngate_lv_base.and(rfnmos_mk).not(rfnmos_exc) - -# rfnmos - HV -rfngate_hv = ngate_hv_base.and(rfnmoshv_mk).not(rfnmos_exc) - -# =============== -# --- RF-PMOS --- -# =============== - -logger.info('Starting RF-PMOS DERIVATIONS') - -# rfpmos - LV -rfpgate_lv = pgate_lv_base.and(rfpmos_mk).not(rfpmos_exc) - -# rfpmos - HV -rfpgate_hv = pgate_hv_base.and(rfpmoshv_mk).not(rfpmos_exc) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_extraction.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_extraction.lvs deleted file mode 100644 index 37a20a491d..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/rfmos_extraction.lvs +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -# ================================ -# ----- RF-MOSFET EXTRACTION ----- -# ================================ - -logger.info('Starting RF-MOSFET EXTRACTION') - -# =============== -# --- RF-NMOS --- -# =============== - -# rfnmos - LV -logger.info('Extraction of RF-NMOS-LV transistor') -extract_devices(mos4('rfnmos'), - { 'SD' => nsd_fet, - 'G' => rfngate_lv, - 'W' => pwell, - 'tS' => nsd_fet, - 'tD' => nsd_fet, - 'tG' => poly_con, - 'tW' => ptap, - }) - -# rfnmos - HV -logger.info('Extraction of RF-NMOS-HV transistor') -extract_devices(mos4('rfnmoshv'), - { 'SD' => nsd_fet, - 'G' => rfngate_hv, - 'W' => pwell, - 'tS' => nsd_fet, - 'tD' => nsd_fet, - 'tG' => poly_con, - 'tW' => ptap, - }) - -# =============== -# --- RF-PMOS --- -# =============== - -# rfpmos - LV -logger.info('Extraction of RF-PMOS-LV transistor') -extract_devices(mos4('rfpmos'), - { 'SD' => psd_fet, - 'G' => rfpgate_lv, - 'tS' => psd_fet, - 'tD' => psd_fet, - 'tG' => poly_con, - 'W' => nwell_drw, - 'tW' => ntap, - }) - -# rfpmos - HV -logger.info('Extraction of RF-PMOS-HV transistor') -extract_devices(mos4('rfpmoshv'), - { 'SD' => psd_fet, - 'G' => rfpgate_hv, - 'tS' => psd_fet, - 'tD' => psd_fet, - 'tG' => poly_con, - 'W' => nwell_drw, - 'tW' => ntap, - }) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_connections.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_connections.lvs deleted file mode 100644 index f922e2a681..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_connections.lvs +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#=============================== -# ------ Taps CONNECTIONS ------ -#=============================== - -logger.info('Starting LVS Taps CONNECTIONS') - -# ntap1 -connect(ntap1_tie, cont_drw) -connect(ntap1_well, nwell_drw) - -# ptap1 -connect(ptap1_tie, cont_drw) -connect(ptap1_sub, pwell) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_derivations.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_derivations.lvs deleted file mode 100644 index 43bb65fd8c..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_derivations.lvs +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#=============================== -# ------ Taps DERIVATIONS ------ -#=============================== - -logger.info('Starting Taps DERIVATIONS') - -taps_exclude = gatpoly.join(nsd_drw).join(trans_drw) - .join(emwind_drw).join(emwihv_drw).join(salblock_drw) - .join(polyres_drw).join(extblock_drw).join(res_drw) - .join(activ_mask).join(recog_diode).join(recog_esd) - .join(ind_drw).join(ind_pin) - -# === ntap1 === -ntap1_exc = pwell.join(psd_drw).join(taps_exclude) - -ntap1_tie = nactiv.and(ntap1_mk).extents.not(ntap1_exc) -ntap1_well = ntap1_mk.covering(ntap1_tie) - -# === ptap1 === -ptap1_exc = nwell_drw.join(taps_exclude) - -ptap1_tie = pactiv.and(ptap1_mk).extents.not(ptap1_exc) -ptap1_sub = pwell.covering(ptap1_tie) diff --git a/flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_extraction.lvs b/flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_extraction.lvs deleted file mode 100644 index d6cc82db07..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/rule_decks/tap_extraction.lvs +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#================================ -# ------ EFUSE EXTRACTIONS ------ -#================================ - -logger.info('Starting Taps EXTRACTION') - -# ntap1 -logger.info('Extracting ntap1 device') -extract_devices(diode('ntap1', CustomTap), { 'N' => ntap1_tie, 'P' => ntap1_well }) - -# ptap1 -logger.info('Extracting ptap1 device') -extract_devices(diode('ptap1', CustomTap), { 'N' => ptap1_tie, 'P' => ptap1_sub }) diff --git a/flow/platforms/ihp-sg13g2/lvs/run_lvs.py b/flow/platforms/ihp-sg13g2/lvs/run_lvs.py deleted file mode 100644 index 5fdaabc0cd..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/run_lvs.py +++ /dev/null @@ -1,376 +0,0 @@ -# ========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -# ========================================================================== - -"""Run IHP 130nm BiCMOS Open Source PDK - SG13G2 LVS. - -Usage: - run_lvs.py (--help| -h) - run_lvs.py (--layout=) (--netlist=) - [--run_dir=] [--topcell=] [--run_mode=] - [--no_net_names] [--spice_comments] [--net_only] [--no_simplify] - [--no_series_res] [--no_parallel_res] [--combine_devices] [--top_lvl_pins] - [--purge] [--purge_nets] [--verbose] - -Options: - --help -h Displays this help message. - --layout= Specifies the file path of the input GDS file. - --netlist= Specifies the file path of the input netlist file. - --run_dir= Run directory to save all the generated results [default: pwd] - --topcell= Specifies the name of the top cell to be used. - --run_mode= Selects the allowed KLayout mode. (flat, deep). [default: deep] - --no_net_names Omits net names in the extracted netlist. - --spice_comments Includes netlist comments in the extracted netlist. - --net_only Generates netlist objects only in the extracted netlist. - --no_simplify Disables simplification for both layout and schematic. - --no_series_res Prevents simplification of series resistors for both layout and schematic. - --no_parallel_res Prevents simplification of parallel resistors for both layout and schematic. - --combine_devices Enables device combination for both layout and schematic netlists. - --top_lvl_pins Creates pins for top-level circuits in both layout and schematic netlists. - --purge Removes unused nets from both layout and schematic netlists. - --purge_nets Purges floating nets from both layout and schematic netlists. - --verbose Enables detailed rule execution logs for debugging purposes. -""" - -from docopt import docopt -import os -import logging -import klayout.db -from datetime import datetime -from subprocess import check_call -import time - - -def check_klayout_version(): - """ - Check 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] < 29: - logging.error("Prerequisites at a minimum: KLayout 0.29.0") - logging.error( - "Using this klayout version has not been assessed. Limits are unknown" - ) - exit(1) - - logging.info(f"Your Klayout version is: {klayout_v_}") - - -def check_layout_type(layout_path): - """ - Checks if the layout provided is GDS2 or OASIS. Otherwise, kill the process. - - 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 GDS2 or OASIS format, please recheck." - ) - exit(1) - - return layout_path - - -def get_top_cell_names(gds_path): - """ - 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 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. Use --topcell to determine which topcell you want." - ) - exit(1) - else: - topcell = layout_topcells[0] - - return topcell - - -def generate_klayout_switches(arguments, layout_path, netlist_path): - """ - 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() - - if arguments["--run_mode"] in ["flat", "deep"]: - run_mode = arguments["--run_mode"] - else: - logging.error("Allowed klayout modes are (flat , deep) only") - exit(1) - - switches = { - "run_mode": run_mode, - "no_net_names": "true" if arguments.get("--no_net_names") else "false", - "spice_comments": "true" if arguments.get("--spice_comments") else "false", - "net_only": "true" if arguments.get("--net_only") else "false", - "top_lvl_pins": "true" if arguments.get("--top_lvl_pins") else "false", - "no_simplify": "true" if arguments.get("--no_simplify") else "false", - "no_series_res": "true" if arguments.get("--no_series_res") else "false", - "no_parallel_res": "true" if arguments.get("--no_parallel_res") else "false", - "combine_devices": "true" if arguments.get("--combine_devices") else "false", - "purge": "true" if arguments.get("--purge") else "false", - "purge_nets": "true" if arguments.get("--purge_nets") else "false", - "verbose": "true" if arguments.get("--verbose") else "false", - "topcell": get_run_top_cell_name(arguments, layout_path), - "input": os.path.abspath(layout_path), - "schematic": os.path.abspath(netlist_path), - } - - return switches - - -def build_switches_string(sws: dict): - """ - Build switches string from dictionary. - - Parameters - ---------- - sws : dict - Dictionary that holds the Antenna switches. - """ - return " ".join(f"-rd {k}={v}" for k, v in sws.items()) - - -def check_lvs_results(results_db_files: list): - """ - 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 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 SG13G2 LVS 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}_extracted.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 existence - layout_path = arguments["--layout"] - layout_path = os.path.abspath(os.path.expanduser(layout_path)) - if not os.path.exists(layout_path): - 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 existence - netlist_path = arguments["--netlist"] - netlist_path = os.path.abspath(os.path.expanduser(netlist_path)) - if not os.path.exists(netlist_path): - 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__)), "sg13g2.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) - - -# ================================================================ -# -------------------------- MAIN -------------------------------- -# ================================================================ - - -if __name__ == "__main__": - # arguments - arguments = docopt(__doc__, version="RUN LVS: 1.0") - - 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) - - # logs format - 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", - ) - - # Start of execution time - t0 = time.time() - - # Calling main function - main(lvs_run_dir, arguments) - - # End of execution time - logging.info("Total execution time {}s".format(time.time() - t0)) diff --git a/flow/platforms/ihp-sg13g2/lvs/sg13g2.lvs b/flow/platforms/ihp-sg13g2/lvs/sg13g2.lvs deleted file mode 100644 index b0042e5cf9..0000000000 --- a/flow/platforms/ihp-sg13g2/lvs/sg13g2.lvs +++ /dev/null @@ -1,396 +0,0 @@ -#========================================================================== -# Copyright 2024 IHP 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. -# SPDX-License-Identifier: Apache-2.0 -#========================================================================== - -#=========================================================================================================== -# ------------------------------------------ SG13G2 LVS RULE DECK ------------------------------------------ -#=========================================================================================================== - -require 'time' -require 'logger' -require 'etc' - -exec_start_time = Time.now - -logger = Logger.new($stdout) - -logger.formatter = proc do |_severity, datetime, _progname, msg| - "#{datetime}: Memory Usage (" + `pmap #{Process.pid} | tail -1`[10, 40].strip + ") : #{msg} -" -end - -#================================================ -#----------------- FILE SETUP ------------------- -#================================================ - -logger.info("Starting running SG13G2 Klayout LVS runset on #{$input}") -logger.info("Ruby Version for klayout: #{RUBY_VERSION}") - -if $input - if $topcell - source($input, $topcell) - else - source($input) - end -end - -logger.info('Loading database to memory is complete.') - -if $report - logger.info("SG13G2 Klayout LVS runset output at: #{$report}") - report_lvs($report) -else - layout_dir = Pathname.new(RBA::CellView.active.filename).parent.realpath - report_path = layout_dir.join("#{source.cell_name}.lvsdb").to_s - logger.info("SG13G2 Klayout LVS runset output at default location: #{source.cell_name}.lvsdb") - report_lvs($report_path) -end - -#================================================ -#------------------ SWITCHES -------------------- -#================================================ - -logger.info('Evaluate switches.') - -def bool_check?(obj) - obj.to_s.downcase == 'true' -end - -#=== NET NAMES OPTION === -# true: use net names instead of numbers -# false: use numbers for nets -SPICE_WITH_NET_NAMES = !bool_check?($no_net_names) - -logger.info("Extracted netlist with net names: #{SPICE_WITH_NET_NAMES}") - -#=== COMMENTS OPTION === -# true: put in comments with details -# false: no comments -SPICE_WITH_COMMENTS = bool_check?($spice_comments) - -logger.info("Extracted netlist with comments in details: #{SPICE_WITH_COMMENTS}") - -# NET_ONLY -NET_ONLY = bool_check?($net_only) - -logger.info("Selected NET_ONLY option: #{NET_ONLY}") - -# TOP_LVL_PINS -TOP_LVL_PINS = bool_check?($top_lvl_pins) - -logger.info("Selected TOP_LVL_PINS option: #{TOP_LVL_PINS}") - -# COMBINE -COMBINE_DEVICES = bool_check?($combine_devices) - -logger.info("Selected COMBINE DEVICES option: #{COMBINE_DEVICES}") - -# PURGE -PURGE = bool_check?($purge) - -logger.info("Selected PURGE option: #{PURGE}") - -# PURGE_NETS -PURGE_NETS = bool_check?($purge_nets) - -logger.info("Selected PURGE_NETS option: #{PURGE_NETS}") - -# SIMPLIFY -SIMPLIFY = !bool_check?($no_simplify) - -logger.info("Selected SIMPLIFY option: #{SIMPLIFY}") - -# SIMPLIFY -SERIES_RES = !bool_check?($no_series_res) - -logger.info("Selected SERIES_RES option: #{SERIES_RES}") - -# SIMPLIFY -PARALLEL_RES = !bool_check?($no_parallel_res) - -logger.info("Selected PARALLEL_RES option: #{PARALLEL_RES}") - -#=== PRINT DETAILS === -logger.info("Verbose mode: #{$verbose}") -if $verbose == 'true' - verbose(true) -else - verbose(false) -end - -# === RUN MODE === -case $run_mode -when 'deep' - #=== HIER MODE === - deep - logger.info('deep mode is enabled.') -else - #=== FLAT MODE === - flat - logger.info('flat mode is enabled.') -end - -# === Tech Switches === - -#================================================ -# --------------- CUSTOM CLASSES ---------------- -#================================================ - -# %include rule_decks/custom_classes.lvs - -# Instantiate a reader using the new delegate -reader = RBA::NetlistSpiceReader.new(CustomReader.new) - -#=== GET NETLIST === -unless NET_ONLY - if $schematic - schematic($schematic, reader) - logger.info("Netlist file: #{$schematic}") - else - exts = %w[spice cdl cir] - candidates = exts.map { |ext| "#{source.cell_name}.#{ext}" } - netlists = candidates.select { |f| File.exist?(f) } - if netlists.empty? - error("Netlist not found, tried: #{candidates}") - else - schematic(netlists[0], reader) - logger.info("Netlist file: #{netlists[0]}") - end - end -end - -# Instantiate a writer using the new delegate -custom_spice_writer = RBA::NetlistSpiceWriter.new(CustomWriter.new) -custom_spice_writer.use_net_names = SPICE_WITH_NET_NAMES -custom_spice_writer.with_comments = SPICE_WITH_COMMENTS - -if $target_netlist - logger.info("LVS extracted netlist at: #{$target_netlist}") - target_netlist($target_netlist, custom_spice_writer, - "Extracted by KLayout with SG13G2 LVS runset on : #{Time.now.strftime('%d/%m/%Y %H:%M')}") -else - layout_dir = Pathname.new(RBA::CellView.active.filename).parent.realpath - netlist_path = layout_dir.join("#{source.cell_name}_extracted.cir") - target_netlist(netlist_path.to_s, custom_spice_writer, - "Extracted by KLayout with SG13G2 LVS runset on : #{Time.now.strftime('%d/%m/%Y %H:%M')}") - logger.info("SG13G2 Klayout LVS extracted netlist file at: #{source.cell_name}_extracted.cir") -end - -#================================================ -#------------- LAYERS DEFINITIONS --------------- -#================================================ - -# %include rule_decks/layers_definitions.lvs - -#================================================================ -#------------------------- MAIN RUNSET -------------------------- -#================================================================ - -logger.info('Starting SG13G2 LVS runset') - -#================================================ -#------------- LAYERS DERIVATIONS --------------- -#================================================ - -logger.info('Starting base layers derivations') - -#================================== -# ------ GENERAL DERIVATIONS ------ -#================================== - -# %include rule_decks/general_derivations.lvs - -#================================== -# ------ MOSFET DERIVATIONS ------- -#================================== - -# %include rule_decks/mos_derivations.lvs - -#================================== -# ----- RF-MOSFET DERIVATIONS ----- -#================================== - -# %include rule_decks/rfmos_derivations.lvs - -#================================ -# ------ BJT DERIVATIONS -------- -#================================ - -# %include rule_decks/bjt_derivations.lvs - -#================================ -# ----- DIODE DERIVATIONS ------- -#================================ - -# %include rule_decks/diode_derivations.lvs - -#================================ -# ---- RESISTOR DERIVATIONS ----- -#================================ - -# %include rule_decks/res_derivations.lvs - -#================================== -# -------- CAP DERIVATIONS -------- -#================================== - -# %include rule_decks/cap_derivations.lvs - -#================================ -# ------ ESD DERIVATIONS -------- -#================================ - -# %include rule_decks/esd_derivations.lvs - -#================================= -# ----- Inductor DERIVATIONS ----- -#================================= - -# %include rule_decks/ind_derivations.lvs - -#================================ -# ------ Taps DERIVATIONS ------- -#================================ - -# %include rule_decks/tap_derivations.lvs - -#================================================ -#------------ DEVICES CONNECTIVITY -------------- -#================================================ - -# %include rule_decks/devices_connections.lvs - -#================================================ -#------------- DEVICES EXTRACTION --------------- -#================================================ - -logger.info('Starting SG13G2 LVS DEVICES EXTRACTION') - -#================================ -# ----- MOSFET EXTRACTION ------- -#================================ - -# %include rule_decks/mos_extraction.lvs - -#================================ -# ---- RF-MOSFET EXTRACTION ----- -#================================ - -# %include rule_decks/rfmos_extraction.lvs - -#================================ -# ------- BJT EXTRACTION -------- -#================================ - -# %include rule_decks/bjt_extraction.lvs - -#================================ -# ------ DIODE EXTRACTION ------- -#================================ - -# %include rule_decks/diode_extraction.lvs - -#================================ -# ---- RESISTOR EXTRACTIONS ----- -#================================ - -# %include rule_decks/res_extraction.lvs - -#================================== -# --------- CAP EXTRACTION -------- -#================================== - -# %include rule_decks/cap_extraction.lvs - -#================================ -# ------- ESD EXTRACTION -------- -#================================ - -# %include rule_decks/esd_extraction.lvs - -#================================= -# ----- Inductor EXTRACTIONS ----- -#================================= - -# %include rule_decks/ind_extraction.lvs - -#================================ -# ------- Taps EXTRACTIONS ------ -#================================ - -# %include rule_decks/tap_extraction.lvs - -#================================================== -# ------------ COMPARISON PREPARATIONS ------------ -#================================================== - -logger.info('Starting SG13G2 LVS Options Preparations') - -# === Extract Netlist Only === -netlist if NET_ONLY -return if NET_ONLY - -# === Aligns the extracted netlist vs. the schematic === -logger.info('Starting SG13G2 LVS Alignment') -align - -#=== NETLIST OPTIONS === -logger.info('Starting SG13G2 LVS Simplification') -# SIMPLIFY -netlist.simplify if SIMPLIFY -schematic.simplify if SIMPLIFY - -# TOP_LVL_PINS -netlist.make_top_level_pins if TOP_LVL_PINS -schematic.make_top_level_pins if TOP_LVL_PINS - -# COMBINE_DEVICES -netlist.combine_devices if COMBINE_DEVICES -schematic.combine_devices if COMBINE_DEVICES - -# PURGE -netlist.purge if PURGE -schematic.purge if PURGE - -# PURGE_NETS -netlist.purge_nets if PURGE_NETS -schematic.purge_nets if PURGE_NETS - -#=== IGNORE EXTREME VALUES === -max_res(1e9) -min_caps(1e-18) - -# === COMPARISON === -logger.info('Starting SG13G2 LVS Comparison') -compare - -#================================================ -#------------- COMPARISON RESULTS --------------- -#================================================ - -if !compare - logger.info('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') - logger.error("ERROR : Netlists don't match") - logger.info('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') -else - logger.info('==========================================') - logger.info('INFO : Congratulations! Netlists match.') - logger.info('==========================================') -end - -exec_end_time = Time.now -run_time = exec_end_time - exec_start_time -logger.info(format('LVS Total Run time %f seconds', run_time)) diff --git a/flow/platforms/ihp-sg13g2/lvs/sg13g2_lvs.py b/flow/platforms/ihp-sg13g2/lvs/sg13g2_lvs.py new file mode 100644 index 0000000000..a0248b8890 --- /dev/null +++ b/flow/platforms/ihp-sg13g2/lvs/sg13g2_lvs.py @@ -0,0 +1,4644 @@ +# ========================================================================== +# Copyright 2024 IHP 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. +# SPDX-License-Identifier: Apache-2.0 +# ========================================================================== + +"""SG13G2 LVS rule deck, on the klayout Python module. + +A line by line translation of IHP's Ruby runset, sg13g2.lvs and the +rule_decks/*.lvs files it includes (in git history), in their include +order; see flow/util/klayout_lvs.py and docs/user/KLayoutPythonDecks.md. + +The switches are the defaults of the runset's driver, run_lvs.py, which +ORFS does not pass: deep mode, simplification and series/parallel +resistor combination on, everything else off. The extracted netlist +(target_netlist, written with CustomWriter) is not written; the .lvsdb +holds it. +""" + +import re + +import klayout_lvs +from klayout_drc import as_boxes, db, nm, um + +THREADS = 1 + +# true: use net names instead of numbers +SPICE_WITH_NET_NAMES = True +# true: put in comments with details +SPICE_WITH_COMMENTS = False +NET_ONLY = False +TOP_LVL_PINS = False +COMBINE_DEVICES = False +PURGE = False +PURGE_NETS = False +SIMPLIFY = True +SERIES_RES = True +PARALLEL_RES = True + +# $logger and $unit of the runset, set by lvs() +LOGGER = print +UNIT = 0.001 + +# The klayout objects hold the device classes, combiners and extractors +# by pointer; Python keeps them alive. +_keep = [] + + +def _keep_alive(obj): + _keep.append(obj) + return obj + + +def _or(*values): + """Ruby's a || b for parameter values: the first that is not nil.""" + for v in values: + if v is not None: + return v + return None + + +# ================================================ +# --------------- CUSTOM CLASSES ---------------- +# ================================================ + +# custom_classes.lvs + + +def glob_to_case_insensitive_glob(glob): + wildcards = ["*", "?"] + pattern = "" + for c in glob: + if re.match(r"[A-Za-z]", c): + pattern += "[%s%s]" % (c.upper(), c.lower()) + elif c in wildcards: + pattern += c # Keep wildcard characters as they are + else: + # Ruby's Regexp.escape + pattern += re.sub(r"([.*?+^$|()\[\]{}\\/\- ])", r"\\\1", c) + return pattern + + +# globals.lvs + +PREFIX_MAP = { + "sg13_lv_nmos": "M", + "sg13_hv_nmos": "M", + "sg13_lv_pmos": "M", + "sg13_hv_pmos": "M", + "rfnmos": "M", + "rfnmosHV": "M", + "rfpmos": "M", + "rfpmosHV": "M", + "npn13G2": "Q", + "npn13G2L": "Q", + "npn13G2V": "Q", + "pnpMPA": "Q", + "dantenna": "D", + "dpantenna": "D", + "schottky_nbl1": "D", + "rsil": "R", + "rppd": "R", + "rhigh": "R", + "lvsres": "R", + "SVaricap": "C", + "cap_cmim": "C", + "rfcmim": "C", + "diodevss_4kv": "D", + "diodevss_2kv": "D", + "diodevdd_4kv": "D", + "diodevdd_2kv": "D", + "idiodevss_4kv": "D", + "idiodevss_2kv": "D", + "idiodevdd_4kv": "D", + "idiodevdd_2kv": "D", + "nmoscl_2": "D", + "nmoscl_4": "D", + "ptap1": "R", + "ntap1": "R", + "inductor2": "L", + "inductor3": "L", +} + +CUSTOM_READER = ["M", "C", "R", "Q", "L", "D"] + +RES_DEV = ["rsil", "rppd", "rhigh"] + + +# custom_reader.lvs + + +class CustomReader(db.NetlistSpiceReaderDelegate): + # Cleanup sch for R, C elements + def clean_sch(self, line, element): + # Extracting parameters with values + valid_params = re.findall(r"\b\w+\s*=\s*\S+\b", line) + + if element == "R": + # For 2 term res [ ] + num_terms = 3 + elif element == "C": + # Determine number of terms based on component type + if "varicap" in line.lower(): + num_terms = 5 + elif "rfcmim" in line.lower(): + num_terms = 4 + else: + num_terms = 3 + + line_no_param = " ".join(line.split()[:num_terms]) + return "%s %s" % (line_no_param.strip(), " ".join(valid_params)) + + # Override parse_element method to handle exceptions gracefully + def parse_element(self, line, element): + try: + # clean line: Ruby's line.delete('[]$\\/') deletes [, ], $ + # and /; the backslash escapes the slash + line = re.sub(r"[\[\]$/]", "", line) + # Prep sch for R, C + if element in ("R", "C"): + line = self.clean_sch(line, element) + return super().parse_element(line, element) + except RuntimeError: + if element == "C": + if "varicap" in line.lower(): + return super().parse_element(line, "M") + elif "rfcmim" in line.lower(): + return super().parse_element(line, "Q") + else: + return super().parse_element("%s C=1e-18" % line, element) + elif element == "R": + return super().parse_element("%s R=0" % line, element) + elif element == "D": + if "diodev" in line.lower() or "schottky" in line.lower(): + return super().parse_element(line, "Q") + else: + return super().parse_element(line, element) + elif element == "L": + if "inductor3" in line.lower(): + return super().parse_element("%s L=0" % line, "M") + else: + return super().parse_element("%s L=0" % line, element) + else: + return super().parse_element(line, element) + + # Override the element method to handle different types of elements + def element(self, circuit, ele, name, model, value, nets, params): + if ele in CUSTOM_READER: + self.process_device(ele, circuit, name, model, nets, params) + else: + super().element(circuit, ele, name, model, value, nets, params) + return True + + # Process device element + def process_device(self, ele, circuit, name, model, nets, params): + cls = circuit.netlist().device_class_by_name(model) + if cls is None: + cls = self.create_device_class(ele, circuit, model, len(nets)) + device = circuit.create_device(cls, name) + self.connect_terminals(ele, device, model, nets) + self.map_params(ele, device, model, params) + + # Create or retrieve the device class based on the element type, model + # name, and number of nets. + def create_device_class(self, ele, circuit, model, num_nets): + if ele == "M": + cls = db.DeviceClassMOS4Transistor() + elif ele == "C": + cls = self.create_capacitor(model, num_nets) + elif ele == "R": + cls = self.create_resistor(model, num_nets) + elif ele == "Q": + cls = self.create_bjt(model, num_nets) + elif ele == "L": + cls = DeviceCustomInd(model, num_nets - 1) + elif ele == "D": + cls = self.create_diode(model) + else: + raise ValueError("%s device with model %s is not supported" % (ele, model)) + cls.name = model + circuit.netlist().add(_keep_alive(cls)) + return cls + + # Create a capacitor device class. + def create_capacitor(self, model, num_nets): + if "varicap" in model.lower(): + if num_nets != 4: + raise ValueError("Varicap should have 4 nodes, please recheck") + return DeviceCustomVaractor(model, num_nets - 1) + else: + if num_nets not in (2, 3): + raise ValueError("Capacitor should have 2 or 3 nodes, please recheck") + return DeviceCustomMIM(model) + + # Create a diode device class. + def create_diode(self, model): + if "diodev" in model.lower() or "schottky" in model.lower(): + return Esd3Term() + elif "nmoscl" in model.lower(): + return Esd2Term() + else: + return EnDiode() + + # Create a resistor device class. + def create_resistor(self, model, num_nets): + if any(model.lower().startswith(res) for res in RES_DEV): + return DeviceCustomRes(model, num_nets) + elif num_nets == 2 and "tap" in model.lower(): + return db.DeviceClassDiode() + elif num_nets == 2: + return db.DeviceClassResistor() + elif num_nets == 3: + return db.DeviceClassResistorWithBulk() + else: + raise ValueError("Resistor should have two or three nodes, please recheck") + + # Create a bjt device class. + def create_bjt(self, model, _num_nets): + if "pnp" in model.lower(): + return CustomBJT3() + else: + return CustomBJT4() + + # Connect device terminals based on element type, device, model, and nets. + def connect_terminals(self, ele, device, model, nets): + term_list = self.terminal_list_for_element(ele, model, nets) + for index, t in enumerate(term_list): + device.connect_terminal(t, nets[index] if index < len(nets) else None) + + # Determine terminal list based on element type, model, and nets. + def terminal_list_for_element(self, ele, model, nets): + if ele == "M": + return ["D", "G", "S", "B"] + elif ele == "Q": + return ["C", "B", "E"] if "pnp" in model.lower() else ["C", "B", "E", "S"] + elif ele == "C": + if "varicap" in model.lower(): + return self.gen_term_with_sub(model, len(nets)) + return self.gen_mim_terms(model) + elif ele == "R": + if any(model.lower().startswith(res) for res in RES_DEV): + return self.gen_term_names(model, len(nets)) + elif "tap" in model.lower(): + return ["C", "A"] + else: + return ["A", "B", "W"] if len(nets) == 3 else ["A", "B"] + elif ele == "D": + if "diodevdd" in model.lower(): + return ["B", "E", "C"] + elif "diodevss" in model.lower(): + return ["C", "E", "B"] + elif "schottky" in model.lower(): + return ["E", "B", "C"] + elif "nmoscl" in model.lower(): + return ["C", "A"] + else: + return ["A", "C"] + elif ele == "L": + return self.gen_term_with_sub(model, len(nets)) + else: + return self.gen_term_names(model, len(nets)) + + # Generate terminal names based on model and the number of nets. + def gen_term_names(self, model, size): + return ["%s_%d" % (model, i + 1) for i in range(size)] + + # Generate terminal names based on model and the number of nets. + def gen_mim_terms(self, model): + terms = ["mim_top", "mim_btm"] + if "rfcmim" not in model.lower(): + return terms + terms.append("mim_sub") # Add sub terminal + return terms + + # Generate terminal names based on model and the number of nets. + def gen_term_with_sub(self, model, size): + terms = ["%s_%d" % (model, i + 1) for i in range(size - 1)] + terms.append("%s_sub" % model) # Add sub terminal + return terms + + # Map parameters based on the model type. + def map_params(self, ele, device, model, params): + if ele == "M": + self.map_mos_params(device, params) + elif ele == "Q": + self.map_bjt_params(device, model, params) + elif ele == "C": + self.map_capacitor_params(device, model, params) + elif ele == "R": + self.map_resistor_params(device, model, params) + elif ele == "D": + self.map_diode_params(device, model, params) + elif ele == "L": + self.map_inductor_params(device, params) + else: + raise ValueError( + "%s device with model %s is not supported, please recheck" + % (ele, model) + ) + + # Map parameters for mos devices. + def map_mos_params(self, device, params): + p = params.get + device.set_parameter("W", _or(p("W"), 0.0) * _or(p("M"), 1.0) * 1e6) + device.set_parameter("L", _or(p("L"), 0.0) * 1e6) + + # Map parameters for a BJT device. + def map_bjt_params(self, device, model, params): + p = params.get + if "pnp" in model.lower(): + device.set_parameter( + "AE", _or(p("A"), _or(p("W"), 0.0) * _or(p("L"), 0.0)) * 1e12 + ) + device.set_parameter( + "PE", _or(p("P"), (_or(p("W"), 0.0) + _or(p("L"), 0.0)) * 2) * 1e6 + ) + else: + device.set_parameter( + "AE", _or(p("AE"), _or(p("WE"), 0.0) * _or(p("LE"), 0.0)) * 1e12 + ) + device.set_parameter( + "PE", + _or(p("PE"), (_or(p("WE"), 0.0) + _or(p("LE"), 0.0)) * 2) * 1e6, + ) + device.set_parameter("NE", _or(p("M"), p("NE"), 1.0)) + device.set_parameter("m", _or(p("M"), p("NE"), 1.0)) + + # Map parameters for a diode device. + def map_diode_params(self, device, model, params): + p = params.get + m = model.lower() + if not ("diodev" in m or "schottky" in m or "nmoscl" in m): + device.set_parameter( + "A", _or(p("A"), _or(p("W"), 0.0) * _or(p("L"), 0.0)) * 1e12 + ) + device.set_parameter( + "P", _or(p("P"), (_or(p("W"), 0.0) + _or(p("L"), 0.0)) * 2) * 1e6 + ) + device.set_parameter("m", _or(p("M"), 1.0)) + + # Map parameters for a capacitor device. + def map_capacitor_params(self, device, model, params): + p = params.get + device.set_parameter("w", _or(p("W"), 0.0) * 1e6) + device.set_parameter("l", _or(p("L"), 0.0) * 1e6) + if "cap_cmim" in model.lower(): + device.set_parameter("m", _or(p("M"), p("MF"), 1.0)) + if "mim" in model.lower(): + device.set_parameter( + "A", _or(p("A"), _or(p("W"), 0.0) * _or(p("L"), 0.0)) * 1e12 + ) + device.set_parameter( + "P", _or(p("P"), (_or(p("W"), 0.0) + _or(p("L"), 0.0)) * 2) * 1e6 + ) + if "rfcmim" not in model.lower(): + return + device.set_parameter("wfeed", _or(p("WFEED"), 0.0) * 1e6) + + # Map parameters for a resistor device. + def map_resistor_params(self, device, model, params): + p = params.get + if "tap" in model.lower(): + device.set_parameter( + "A", _or(p("A"), _or(p("W"), 0.0) * _or(p("L"), 0.0)) * 1e12 + ) + device.set_parameter( + "P", + _or(p("P"), p("PERIM"), (_or(p("W"), 0.0) + _or(p("L"), 0.0)) * 2) + * 1e6, + ) + elif any(model.lower().startswith(res) for res in RES_DEV): + device.set_parameter("w", _or(p("W"), 0.0) * 1e6) + device.set_parameter("l", _or(p("L"), 0.0) * 1e6) + device.set_parameter("ps", _or(p("PS"), 0.0) * 1e6) + device.set_parameter("b", _or(p("B"), 0.0)) + device.set_parameter("m", _or(p("M"), 1.0)) + else: + device.set_parameter( + "W", _or(p("W"), p("WIDTH"), 0.0) * _or(p("M"), 1.0) * 1e6 + ) + device.set_parameter( + "L", _or(p("L"), p("LENGTH"), 0.0) * _or(p("S"), 1.0) * 1e6 + ) + device.set_parameter( + "R", _or(p("R"), 0.0) * _or(p("S"), 1.0) / _or(p("M"), 1.0) + ) + + # Map parameters for an inductor device. + def map_inductor_params(self, device, params): + p = params.get + device.set_parameter("w", _or(p("W"), 0.0) * 1e6) + device.set_parameter("s", _or(p("S"), 0.0) * 1e6) + device.set_parameter("d", _or(p("D"), 0.0) * 1e6) + device.set_parameter("nr_r", _or(p("NR_R"), 0.0)) + + +# custom_devices.lvs + + +def add_parameters(dc, *names): + for name in names: + dc.add_parameter(db.DeviceParameterDefinition(name)) + + +def add_terminals(dc, name, num, sub_en): + for i in range(1, num + 1): + terminal_name = "%s_%d" % (name, i) + ter = dc.add_terminal(db.DeviceTerminalDefinition(terminal_name)) + ter.name = terminal_name + if sub_en != 1: + return + terminal_name = "%s_sub" % name + ter = dc.add_terminal(db.DeviceTerminalDefinition(terminal_name)) + ter.name = terminal_name + + +def set_combiner(dc, combiner): + dc.combiner = combiner and _keep_alive(combiner) + + +class DeviceCustomRes(db.DeviceClassResistor): + def __init__(self, name, num): + super().__init__() + self.clear_parameters() + self.clear_terminals() + add_parameters(self, "w", "l", "ps", "b") + self.add_parameter(db.DeviceParameterDefinition("m", "multiplier", 1, True)) + if SERIES_RES or PARALLEL_RES: + set_combiner(self, RESDeviceCombiner()) + else: + set_combiner(self, None) + add_terminals(self, name, num, 0) + + +class CustomBJT3(db.DeviceClassBJT3Transistor): + def __init__(self): + super().__init__() + self.add_parameter(db.DeviceParameterDefinition("m", "multiplier", 1, True)) + set_combiner(self, BJTDeviceCombiner()) + self.enable_parameter("AE", True) + self.enable_parameter("PE", True) + self.enable_parameter("NE", True) + + +class CustomBJT4(db.DeviceClassBJT4Transistor): + def __init__(self): + super().__init__() + self.add_parameter(db.DeviceParameterDefinition("m", "multiplier", 1, True)) + set_combiner(self, BJTDeviceCombiner()) + self.enable_parameter("AE", True) + self.enable_parameter("PE", True) + self.enable_parameter("NE", True) + + +class DeviceCustomInd(db.DeviceClassInductor): + def __init__(self, name, num): + super().__init__() + self.clear_parameters() + self.clear_terminals() + self.clear_equivalent_terminal_ids() + add_parameters(self, "w", "s", "d", "nr_r") + add_terminals(self, name, num, 1) + equal_ind_parameters = db.EqualDeviceParameters( + self.parameter_id("w"), 0.0, 0.05 + ) + equal_ind_parameters += db.EqualDeviceParameters( + self.parameter_id("s"), 0.0, 0.05 + ) + equal_ind_parameters += db.EqualDeviceParameters( + self.parameter_id("d"), 0.0, 0.05 + ) + self.equal_parameters = equal_ind_parameters + set_combiner(self, None) + self.supports_serial_combination = False + self.supports_parallel_combination = False + + +class DeviceCustomVaractor(db.DeviceClassCapacitorWithBulk): + def __init__(self, name, num): + super().__init__() + self.clear_parameters() + self.clear_terminals() + add_parameters(self, "w", "l") + add_terminals(self, name, num, 1) + + +class RES2(db.DeviceClassResistor): + def __init__(self): + super().__init__() + self.enable_parameter("W", True) + self.enable_parameter("L", True) + self.enable_parameter("R", False) + + +class EnDiode(db.DeviceClassDiode): + def __init__(self): + super().__init__() + equal_diode_parameters = db.EqualDeviceParameters( + self.parameter_id("A"), 0.0, 0.01 + ) + equal_diode_parameters += db.EqualDeviceParameters( + self.parameter_id("P"), 0.0, 0.01 + ) + self.equal_parameters = equal_diode_parameters + set_combiner(self, DiodeDeviceCombiner()) + self.supports_serial_combination = True + self.supports_parallel_combination = True + self.add_parameter(db.DeviceParameterDefinition("m", "multiplier", 1, True)) + self.enable_parameter("A", True) + self.enable_parameter("P", True) + self.enable_parameter("m", True) + + +class CustomTap(db.DeviceClassDiode): + def __init__(self): + super().__init__() + self.clear_parameters() + self.add_parameter(db.DeviceParameterDefinition("A", "Area", 0, True)) + self.add_parameter(db.DeviceParameterDefinition("P", "Perimeter", 0, True)) + self.enable_parameter("A", True) + self.enable_parameter("P", True) + + +class Esd3Term(db.DeviceClassBJT3Transistor): + def __init__(self): + super().__init__() + self.clear_parameters() + self.add_parameter(db.DeviceParameterDefinition("m", "area", 1, True)) + self.enable_parameter("m", True) + + +class Esd2Term(db.DeviceClassDiode): + def __init__(self): + super().__init__() + self.clear_parameters() + self.add_parameter(db.DeviceParameterDefinition("m", "area", 1, True)) + self.enable_parameter("m", True) + + +# custom_combiner.lvs + + +def same_net(a, b, name): + a_net = a.net_for_terminal(name) + b_net = b.net_for_terminal(name) + same_po = None + if a_net and b_net: + same_po = a_net.expanded_name() == b_net.expanded_name() + return bool(a_net and b_net and same_po) + + +def supp_parallel(a, b, net1, net2): + a_net1 = a.net_for_terminal(net1) + a_net2 = a.net_for_terminal(net2) + b_net1 = b.net_for_terminal(net1) + b_net2 = b.net_for_terminal(net2) + if not (a_net1 and b_net1 and a_net2 and b_net2): + return False + same_po = ( + a_net1.expanded_name() == b_net1.expanded_name() + and a_net2.expanded_name() == b_net2.expanded_name() + ) + diff_po = ( + a_net1.expanded_name() == b_net2.expanded_name() + and a_net2.expanded_name() == b_net1.expanded_name() + ) + return same_po or diff_po + + +def supp_series(a, b, net1, net2): + a_net1 = a.net_for_terminal(net1) + a_net2 = a.net_for_terminal(net2) + b_net1 = b.net_for_terminal(net1) + b_net2 = b.net_for_terminal(net2) + if not (a_net1 and b_net1 and a_net2 and b_net2): + return False + series_con_a1 = ( + a_net1.expanded_name() == b_net1.expanded_name() + or a_net1.expanded_name() == b_net2.expanded_name() + ) + series_con_a2 = ( + a_net2.expanded_name() == b_net1.expanded_name() + or a_net2.expanded_name() == b_net2.expanded_name() + ) + series_con = series_con_a1 or series_con_a2 + if series_con_a1: + if a_net1.expanded_name() == b_net1.expanded_name(): + a.connect_terminal(0, b_net2) + else: + a.connect_terminal(0, b_net1) + if series_con_a2: + if a_net2.expanded_name() == b_net1.expanded_name(): + a.connect_terminal(1, b_net2) + else: + a.connect_terminal(1, b_net1) + return series_con + + +def same_parameter(a, b, name): + return abs(a.parameter(name) - b.parameter(name)) < 1e-9 + + +class MIMCAPNDeviceCombiner(db.GenericDeviceCombiner): + def combine_devices(self, a, b): + if not (same_net(a, b, "mim_top") and same_net(a, b, "mim_btm")): + return False + if not (same_parameter(a, b, "w") and same_parameter(a, b, "l")): + return False + a.set_parameter("m", a.parameter("m") + b.parameter("m")) + b.disconnect_terminal("mim_top") + b.disconnect_terminal("mim_btm") + if "rfcmim" in b.name.lower(): + b.disconnect_terminal("mim_sub") + return True + + +class DiodeDeviceCombiner(db.GenericDeviceCombiner): + def combine_devices(self, a, b): + if not (same_net(a, b, "A") and same_net(a, b, "C")): + return False + if not (same_parameter(a, b, "A") and same_parameter(a, b, "P")): + return False + a.set_parameter("m", a.parameter("m") + b.parameter("m")) + b.disconnect_terminal("A") + b.disconnect_terminal("C") + return True + + +class BJTDeviceCombiner(db.GenericDeviceCombiner): + def combine_devices(self, a, b): + bjt3_nets = ["C", "B", "E"] + bjt4_nets = ["C", "B", "E", "S"] + bjt_nets = bjt3_nets if "pnp" in a.device_class().name.lower() else bjt4_nets + if not all(same_net(a, b, net) for net in bjt_nets): + return False + if not all(same_parameter(a, b, param) for param in ["AE", "PE"]): + return False + a.set_parameter("m", a.parameter("m") + b.parameter("m")) + a.set_parameter("NE", a.parameter("NE") + b.parameter("NE")) + for term in bjt_nets: + b.disconnect_terminal(term) + return True + + +class RESDeviceCombiner(db.GenericDeviceCombiner): + def combine_devices(self, a, b): + res_nets = [0, 1] # Using id instead of names + if supp_parallel(a, b, 0, 1): + if not PARALLEL_RES: + return False + if not all(same_parameter(a, b, param) for param in ["w", "l", "ps", "b"]): + return False + a.set_parameter("m", a.parameter("m") + b.parameter("m")) + elif supp_series(a, b, 0, 1): + if not SERIES_RES: + return False + if not all(same_parameter(a, b, param) for param in ["w", "ps", "b", "m"]): + return False + a.set_parameter("l", a.parameter("l") + b.parameter("l")) + else: + return False + for term in res_nets: + b.disconnect_terminal(term) + return True + + +# custom_extractor.lvs + +# The Ruby runset passes RBA::Metrics::Projection as the second positional +# argument of space_check, notch_check and separation_check, which is +# whole_edges: true, with Euclidian metrics. These functions do the same. + + +def get_uniq_length(sel_edges): + lengths = [] + for edge in sel_edges.each(): + if edge.length() not in lengths: + lengths.append(edge.length()) + return lengths[0] if len(lengths) == 1 else 0.0 + + +def get_min_max_length(sel_edges): + lengths = [edge.length() for edge in sel_edges.each()] + if not lengths: + return None, None + return min(lengths), max(lengths) + + +def _min(values): + return min(values) if values else None + + +class GeneralNTerminalExtractor(db.GenericDeviceExtractor): + def __init__(self, name, num): + super().__init__() + self.name = name + self.num = num + self.dev_name = name + + def setup(self): + self.define_layers() + name = self.name + if any(name.lower().startswith(res) for res in RES_DEV): + self.reg_dev = DeviceCustomRes(name, self.num) + elif "varicap" in name.lower(): + self.reg_dev = DeviceCustomVaractor(name, self.num) + elif name.lower().startswith("ind"): + self.reg_dev = DeviceCustomInd(name, self.num) + else: + raise ValueError( + "Custom-Class for %s device is not supported yet, please recheck" % name + ) + self.register_device_class(_keep_alive(self.reg_dev)) + + def get_connectivity(self, _layout, layers): + dev = layers[0] + ports = layers[1] + meas_mk = layers[2] + dev_mk = layers[3] + + conn = db.Connectivity() + conn.connect(dev, dev) + conn.connect(dev, dev_mk) + conn.connect(dev, meas_mk) + conn.connect(ports, dev_mk) + conn.connect(meas_mk, dev_mk) + + if self.name.lower().startswith("ind") or "varicap" in self.name.lower(): + sub_mk = layers[4] + conn.connect(sub_mk, dev_mk) + return conn + + def extract_devices(self, layer_geometry): + dev, ports, meas_mk, dev_mk, sub_mk = (list(layer_geometry) + [None])[:5] + for region in dev_mk.merged().each(): + if ports.count() != self.num: + LOGGER( + "%s device terminals (%d) don't touch device marker correctly" + % (self.dev_name, self.num) + ) + LOGGER( + "No. of ports exist for %s is %d, should be %d" + % (self.dev_name, ports.count(), self.num) + ) + else: + device = self.create_device() + self.set_device_parameters(device, region, dev, ports, meas_mk, dev_mk) + self.define_and_sort_terminals(device, ports, sub_mk) + + def define_layers(self): + self.define_layer("core", "core Layer") + self.define_layer("ports", "Connect Terminal") + self.define_layer("meas_mk", "Measuring parameters marker") + self.define_layer("dev_mk", "Device Marker") + if self.name.lower().startswith("ind") or "varicap" in self.name.lower(): + self.define_layer("sub_mk", "Substrate Marker") + + def set_device_parameters(self, device, region, dev, ports, meas_mk, dev_mk): + name = self.name + if any(name.lower().startswith(res) for res in RES_DEV): + width, length, poly_sp, bends = self.calc_res_params(dev, ports, meas_mk) + device.set_parameter("w", width * UNIT) + device.set_parameter("l", length * UNIT) + device.set_parameter("ps", poly_sp * UNIT) + device.set_parameter("b", bends) + elif "varicap" in name.lower(): + width, length = self.calc_varicap_params(dev, ports, meas_mk, dev_mk) + device.set_parameter("w", width * UNIT) + device.set_parameter("l", length * UNIT) + elif name.lower().startswith("ind"): + width, space, diameter, no_turns = self.calc_ind_params( + dev, ports, meas_mk, dev_mk, region + ) + device.set_parameter("w", width * UNIT) + device.set_parameter("s", space * UNIT) + device.set_parameter("d", diameter * UNIT) + device.set_parameter("nr_r", no_turns) + + def calc_res_params(self, dev, ports, meas_mk): + width_edges = dev.edges() & ports.edges() + width = get_uniq_length(width_edges) + length_edges = dev.edges().interacting(width_edges) - width_edges + length, _ = get_min_max_length(length_edges) + corners = meas_mk.interacting(dev).corners().not_interacting(ports).count() + bends = corners // 4 + poly_sp = None + if bends > 0: + poly_sp_polygon = meas_mk.interacting(dev) + poly_sp = self.get_notch_min(poly_sp_polygon, 10 * length) + length = length + width + width = _or(width, 0) + length = _or(length, 0) + poly_sp = _or(poly_sp, 0) + bends = _or(bends, 0) + return width, length, poly_sp, bends + + def calc_varicap_params(self, dev, _ports, meas_mk, _dev_mk): + width_edges = dev.edges().not_interacting(meas_mk.edges()) + length, width = get_min_max_length(width_edges) + width = _or(width, 0) + length = _or(length, 0) + return width, length + + def calc_ind_params(self, dev, ports, meas_mk, dev_mk, region): + _, max_mk_len = get_min_max_length(dev_mk.edges()) + meas_sel = meas_mk.merged() & db.Region(region) + width = self.get_width_val(meas_sel, max_mk_len) + space = self.get_space_val(meas_sel, max_mk_len) + no_turns_init = meas_mk.merged().count() + no_turns = no_turns_init + diam_extents = dev.extents().edges() + diam_edge_exc = diam_extents.interacting(ports) + diam_edge = diam_extents.not_interacting(diam_edge_exc) + diameter = diam_edge.length() + diameter = diameter - (2 * (no_turns - 1) * space) - (2 * no_turns * width) + diameter = 0 if diameter < 0 else diameter + width = _or(width, 0) + space = _or(space, 0) + diameter = _or(diameter, 0) + no_turns = _or(no_turns, 1) + return width, space, diameter, no_turns + + def define_and_sort_terminals(self, device, ports, sub_mk): + name = self.name + substrings = ["varicap"] + sorted_ports = None + for substring in substrings: + if substring in name: + sorted_ports = ports + break # Exit loop if a match is found + if sorted_ports is None: + sorted_ports = self.sort_polygons(ports) + + if name.lower().startswith("ind") or "varicap" in name.lower(): + if sub_mk.is_empty(): + LOGGER( + "Sub terminal for %s device doesn't exist, please recheck" + % self.dev_name + ) + return None + else: + self.define_terminal( + device, + self.reg_dev.terminal_id("%s_sub" % name), + 4, + sub_mk[0], + ) + for i in range(1, self.num + 1): + self.define_terminal( + device, + self.reg_dev.terminal_id("%s_%d" % (name, i)), + 1, + sorted_ports[i - 1], + ) + + def sort_polygons(self, polygons): + con_polygons = [] + for ports_pl in polygons.merged().each(): + con_edges = [] + for con_ed in ports_pl.each_edge(): + for pt in ([con_ed.x1, con_ed.y1], [con_ed.x2, con_ed.y2]): + if pt not in con_edges: + con_edges.append(pt) + con_polygons.append(con_edges) + sorted_ports_polygons = sorted(con_polygons, key=lambda pl: pl[0]) + sorted_ports = [] + for sorted_pl in sorted_ports_polygons: + ports_pl = db.Polygon( + [ + db.Point(sorted_pl[0][0], sorted_pl[0][1]), + db.Point(sorted_pl[1][0], sorted_pl[1][1]), + db.Point(sorted_pl[2][0], sorted_pl[2][1]), + db.Point(sorted_pl[3][0], sorted_pl[3][1]), + ] + ) + sorted_ports.append(ports_pl) + return sorted_ports + + def get_sep_val(self, sel_edges, sep_edges, sep_val): + sep_paris = sel_edges.separation_check(sep_edges, sep_val, True) + return _min([edge.distance() for edge in sep_paris.each()]) + + def get_space_val(self, sel_polygon, sep_val): + space_paris = sel_polygon.space_check(sep_val, True) + return _min([edge.distance() for edge in space_paris.each()]) + + def get_width_val(self, sel_polygon, width_val): + width_paris = sel_polygon.width_check( + width_val, metrics=db.Metrics.Projection, min_projection=10 + ) + width_values = [edge.distance() for edge in width_paris.each()] + width_values = [v for v in width_values if v != 0] + return _min(width_values) + + def get_notch_min(self, sel_polygon, sep_val): + space_paris = sel_polygon.notch_check(sep_val, True) + return _min([edge.distance() for edge in space_paris.each()]) + + def get_notch_max(self, sel_polygon, sep_val): + space_paris = sel_polygon.notch_check(sep_val, True) + values = [edge.distance() for edge in space_paris.each()] + return max(values) if values else None + + +# custom_mim_extractor.lvs + + +class DeviceCustomMIM(db.DeviceClassCapacitor): + def __init__(self, name): + super().__init__() + self.clear_parameters() + self.clear_terminals() + self.clear_equivalent_terminal_ids() + self.add_parameter(db.DeviceParameterDefinition("w", "width", 0, False)) + self.add_parameter(db.DeviceParameterDefinition("l", "length", 0, False)) + self.add_parameter(db.DeviceParameterDefinition("A", "area", 0, True)) + self.add_parameter(db.DeviceParameterDefinition("P", "perimeter", 0, True)) + self.add_parameter(db.DeviceParameterDefinition("m", "multiplier", 1, True)) + ter1 = self.add_terminal(db.DeviceTerminalDefinition("mim_top")) + ter2 = self.add_terminal(db.DeviceTerminalDefinition("mim_btm")) + ter1.name = "mim_top" + ter2.name = "mim_btm" + if "rfcmim" not in name.lower(): + return + self.add_parameter(db.DeviceParameterDefinition("wfeed", "feed width", 0, True)) + sub_ter = self.add_terminal(db.DeviceTerminalDefinition("mim_sub")) + sub_ter.name = "mim_sub" + + +class MIMCAPExtractor(db.GenericDeviceExtractor): + def __init__(self, name): + super().__init__() + self.name = name + self.dev_name = name + + def setup(self): + self.define_layers() + self.reg_dev = DeviceCustomMIM(self.name) + if "rfcmim" in self.name.lower(): + set_combiner(self.reg_dev, None) + else: + set_combiner(self.reg_dev, MIMCAPNDeviceCombiner()) + self.register_device_class(_keep_alive(self.reg_dev)) + + def get_connectivity(self, _layout, layers): + dev = layers[0] + top_mim = layers[1] + btm_mim = layers[2] + dev_mk = layers[3] + meas_mk = layers[4] + + conn = db.Connectivity() + conn.connect(dev, dev) + conn.connect(dev, dev_mk) + conn.connect(dev, meas_mk) + conn.connect(top_mim, dev_mk) + conn.connect(btm_mim, dev_mk) + + if "rfcmim" in self.name.lower(): + sub_mk = layers[5] + conn.connect(sub_mk, dev_mk) + return conn + + def extract_devices(self, layer_geometry): + dev, top_mim, btm_mim, dev_mk, meas_mk, sub_mk = ( + list(layer_geometry) + [None] + )[:6] + for _region in dev_mk.merged().each(): + if top_mim.count() != 1: + LOGGER( + "No. of ports exist for %s topmetal is %d, should be 1" + % (self.dev_name, top_mim.count()) + ) + elif btm_mim.count() != 1: + LOGGER( + "No. of ports exist for %s btmmetal is %d, should be 1" + % (self.dev_name, btm_mim.count()) + ) + else: + device = self.create_device() + self.set_device_parameters(device, dev, dev_mk, meas_mk) + self.define_terminals(device, top_mim, btm_mim, sub_mk) + + def define_layers(self): + self.define_layer("core", "core Layer") + self.define_layer("top_mim", "Connect Terminal for top mim") + self.define_layer("btm_mim", "Connect Terminal for btm mim") + self.define_layer("dev_mk", "Device Marker") + self.define_layer("meas_mk", "Measuring parameters marker") + if "rfcmim" not in self.name.lower(): + return + self.define_layer("sub_mk", "Substrate layer") + + def set_device_parameters(self, device, dev, dev_mk, meas_mk): + width, length, wfeed = self.calc_cmim_params(dev, dev_mk, meas_mk) + if "rfcmim" in self.name.lower(): + device.set_parameter("l", width * UNIT) + device.set_parameter("w", length * UNIT) + device.set_parameter("wfeed", wfeed * UNIT) + else: + device.set_parameter("w", width * UNIT) + device.set_parameter("l", length * UNIT) + device.set_parameter("A", width * length * UNIT * UNIT) + device.set_parameter("P", (width + length) * 2 * UNIT) + + def calc_cmim_params(self, dev, dev_mk, meas_mk): + dev_edges = dev.edges() + width_edges = dev_edges.with_angle(0, False) + len_edges = dev_edges - width_edges + width = get_uniq_length(width_edges) + length = get_uniq_length(len_edges) + wfeed_edges = meas_mk.edges() & dev_mk.edges() + wfeed = get_uniq_length(wfeed_edges) + width = _or(width, 0) + length = _or(length, 0) + wfeed = _or(wfeed, 0) + return width, length, wfeed + + def define_terminals(self, device, top_mim, btm_mim, sub_mk): + if "rfcmim" in self.name.lower(): + if sub_mk.is_empty(): + LOGGER( + "Sub terminal for %s device doesn't exist, please recheck" + % self.dev_name + ) + return None + else: + self.define_terminal( + device, self.reg_dev.terminal_id("mim_sub"), 5, sub_mk[0] + ) + self.define_terminal(device, self.reg_dev.terminal_id("mim_top"), 1, top_mim[0]) + self.define_terminal(device, self.reg_dev.terminal_id("mim_btm"), 2, btm_mim[0]) + + +# ================================================================ +# ------------------------- MAIN RUNSET -------------------------- +# ================================================================ + + +def lvs(deck): + global LOGGER, UNIT + LOGGER = deck.info + UNIT = deck.dbu + + deck.info("Starting running SG13G2 Klayout LVS runset") + + # Instantiate a reader using the new delegate + reader = db.NetlistSpiceReader(_keep_alive(CustomReader())) + + # === GET NETLIST === + deck.read_schematic(reader) + + # ================================================ + # ------------- LAYERS DEFINITIONS --------------- + # ================================================ + + # layers_definitions.lvs + polygons_count = 0 + deck.info("Read in polygons from layers.") + + # Deep mode: the runset's flat mode would merge each input layer here + def get_polygons(lay_no, lay_dt): + return deck.polygons(lay_no, lay_dt) + + nodrc_drw = get_polygons(62, 0) + count = nodrc_drw.count() + deck.info(f"nodrc_drw has {count} polygons") + polygons_count += count + + activ_drw = get_polygons(1, 0) + activ_drw = activ_drw.not_(nodrc_drw) + count = activ_drw.count() + deck.info(f"activ_drw has {count} polygons") + polygons_count += count + + activ_filler = get_polygons(1, 22) + activ_filler = activ_filler.not_(nodrc_drw) + count = activ_filler.count() + deck.info(f"activ_filler has {count} polygons") + polygons_count += count + + # activ org + activ = activ_drw.join(activ_filler) + count = activ.count() + deck.info(f"activ has {count} polygons") + + activ_pin = get_polygons(1, 2) + activ_pin = activ_pin.not_(nodrc_drw) + count = activ_pin.count() + deck.info(f"activ_pin has {count} polygons") + polygons_count += count + + activ_mask = get_polygons(1, 20) + activ_mask = activ_mask.not_(nodrc_drw) + count = activ_mask.count() + deck.info(f"activ_mask has {count} polygons") + polygons_count += count + + activ_nofill = get_polygons(1, 23) + activ_nofill = activ_nofill.not_(nodrc_drw) + count = activ_nofill.count() + deck.info(f"activ_nofill has {count} polygons") + polygons_count += count + + activ_OPC = get_polygons(1, 26) + activ_OPC = activ_OPC.not_(nodrc_drw) + count = activ_OPC.count() + deck.info(f"activ_OPC has {count} polygons") + polygons_count += count + + activ_iOPC = get_polygons(1, 27) + activ_iOPC = activ_iOPC.not_(nodrc_drw) + count = activ_iOPC.count() + deck.info(f"activ_iOPC has {count} polygons") + polygons_count += count + + activ_noqrc = get_polygons(1, 28) + activ_noqrc = activ_noqrc.not_(nodrc_drw) + count = activ_noqrc.count() + deck.info(f"activ_noqrc has {count} polygons") + polygons_count += count + + biwind_drw = get_polygons(3, 0) + biwind_drw = biwind_drw.not_(nodrc_drw) + count = biwind_drw.count() + deck.info(f"biwind_drw has {count} polygons") + polygons_count += count + + biwind_OPC = get_polygons(3, 26) + biwind_OPC = biwind_OPC.not_(nodrc_drw) + count = biwind_OPC.count() + deck.info(f"biwind_OPC has {count} polygons") + polygons_count += count + + gatpoly_drw = get_polygons(5, 0) + gatpoly_drw = gatpoly_drw.not_(nodrc_drw) + count = gatpoly_drw.count() + deck.info(f"gatpoly_drw has {count} polygons") + polygons_count += count + + gatpoly_filler = get_polygons(5, 22) + gatpoly_filler = gatpoly_filler.not_(nodrc_drw) + count = gatpoly_filler.count() + deck.info(f"gatpoly_filler has {count} polygons") + polygons_count += count + + # gatpoly org + gatpoly = gatpoly_drw.join(gatpoly_filler) + count = gatpoly.count() + deck.info(f"gatpoly has {count} polygons") + + gatpoly_pin = get_polygons(5, 2) + gatpoly_pin = gatpoly_pin.not_(nodrc_drw) + count = gatpoly_pin.count() + deck.info(f"gatpoly_pin has {count} polygons") + polygons_count += count + + gatpoly_nofill = get_polygons(5, 23) + gatpoly_nofill = gatpoly_nofill.not_(nodrc_drw) + count = gatpoly_nofill.count() + deck.info(f"gatpoly_nofill has {count} polygons") + polygons_count += count + + gatpoly_OPC = get_polygons(5, 26) + gatpoly_OPC = gatpoly_OPC.not_(nodrc_drw) + count = gatpoly_OPC.count() + deck.info(f"gatpoly_OPC has {count} polygons") + polygons_count += count + + gatpoly_iOPC = get_polygons(5, 27) + gatpoly_iOPC = gatpoly_iOPC.not_(nodrc_drw) + count = gatpoly_iOPC.count() + deck.info(f"gatpoly_iOPC has {count} polygons") + polygons_count += count + + gatpoly_noqrc = get_polygons(5, 28) + gatpoly_noqrc = gatpoly_noqrc.not_(nodrc_drw) + count = gatpoly_noqrc.count() + deck.info(f"gatpoly_noqrc has {count} polygons") + polygons_count += count + + cont_drw = get_polygons(6, 0) + cont_drw = cont_drw.not_(nodrc_drw) + count = cont_drw.count() + deck.info(f"cont_drw has {count} polygons") + polygons_count += count + + cont_OPC = get_polygons(6, 26) + cont_OPC = cont_OPC.not_(nodrc_drw) + count = cont_OPC.count() + deck.info(f"cont_OPC has {count} polygons") + polygons_count += count + + nsd_drw = get_polygons(7, 0) + nsd_drw = nsd_drw.not_(nodrc_drw) + count = nsd_drw.count() + deck.info(f"nsd_drw has {count} polygons") + polygons_count += count + + nsd_block = get_polygons(7, 21) + nsd_block = nsd_block.not_(nodrc_drw) + count = nsd_block.count() + deck.info(f"nsd_block has {count} polygons") + polygons_count += count + + metal1_drw = get_polygons(8, 0) + metal1_drw = metal1_drw.not_(nodrc_drw) + count = metal1_drw.count() + deck.info(f"metal1_drw has {count} polygons") + polygons_count += count + + metal1_filler = get_polygons(8, 22) + metal1_filler = metal1_filler.not_(nodrc_drw) + count = metal1_filler.count() + deck.info(f"metal1_filler has {count} polygons") + polygons_count += count + + metal1_slit = get_polygons(8, 24) + metal1_slit = metal1_slit.not_(nodrc_drw) + count = metal1_slit.count() + deck.info(f"metal1_slit has {count} polygons") + polygons_count += count + + # metal1 org + metal1 = metal1_drw.join(metal1_filler).not_(metal1_slit) + count = metal1.count() + deck.info(f"metal1 has {count} polygons") + + metal1_pin = get_polygons(8, 2) + metal1_pin = metal1_pin.not_(nodrc_drw) + count = metal1_pin.count() + deck.info(f"metal1_pin has {count} polygons") + polygons_count += count + + metal1_mask = get_polygons(8, 20) + metal1_mask = metal1_mask.not_(nodrc_drw) + count = metal1_mask.count() + deck.info(f"metal1_mask has {count} polygons") + polygons_count += count + + metal1_nofill = get_polygons(8, 23) + metal1_nofill = metal1_nofill.not_(nodrc_drw) + count = metal1_nofill.count() + deck.info(f"metal1_nofill has {count} polygons") + polygons_count += count + + metal1_text = deck.labels(8, 25) + metal1_text = metal1_text.not_(nodrc_drw) + count = metal1_text.count() + deck.info(f"metal1_text has {count} polygons") + polygons_count += count + + metal1_OPC = get_polygons(8, 26) + metal1_OPC = metal1_OPC.not_(nodrc_drw) + count = metal1_OPC.count() + deck.info(f"metal1_OPC has {count} polygons") + polygons_count += count + + metal1_noqrc = get_polygons(8, 28) + metal1_noqrc = metal1_noqrc.not_(nodrc_drw) + count = metal1_noqrc.count() + deck.info(f"metal1_noqrc has {count} polygons") + polygons_count += count + + metal1_res = get_polygons(8, 29) + metal1_res = metal1_res.not_(nodrc_drw) + count = metal1_res.count() + deck.info(f"metal1_res has {count} polygons") + polygons_count += count + + metal1_iprobe = get_polygons(8, 33) + metal1_iprobe = metal1_iprobe.not_(nodrc_drw) + count = metal1_iprobe.count() + deck.info(f"metal1_iprobe has {count} polygons") + polygons_count += count + + metal1_diffprb = get_polygons(8, 34) + metal1_diffprb = metal1_diffprb.not_(nodrc_drw) + count = metal1_diffprb.count() + deck.info(f"metal1_diffprb has {count} polygons") + polygons_count += count + + passiv_drw = get_polygons(9, 0) + passiv_drw = passiv_drw.not_(nodrc_drw) + count = passiv_drw.count() + deck.info(f"passiv_drw has {count} polygons") + polygons_count += count + + passiv_pin = get_polygons(9, 2) + passiv_pin = passiv_pin.not_(nodrc_drw) + count = passiv_pin.count() + deck.info(f"passiv_pin has {count} polygons") + polygons_count += count + + passiv_sbump = get_polygons(9, 36) + passiv_sbump = passiv_sbump.not_(nodrc_drw) + count = passiv_sbump.count() + deck.info(f"passiv_sbump has {count} polygons") + polygons_count += count + + passiv_pillar = get_polygons(9, 35) + passiv_pillar = passiv_pillar.not_(nodrc_drw) + count = passiv_pillar.count() + deck.info(f"passiv_pillar has {count} polygons") + polygons_count += count + + passiv_pdl = get_polygons(9, 40) + passiv_pdl = passiv_pdl.not_(nodrc_drw) + count = passiv_pdl.count() + deck.info(f"passiv_pdl has {count} polygons") + polygons_count += count + + metal2_drw = get_polygons(10, 0) + metal2_drw = metal2_drw.not_(nodrc_drw) + count = metal2_drw.count() + deck.info(f"metal2_drw has {count} polygons") + polygons_count += count + + metal2_filler = get_polygons(10, 22) + metal2_filler = metal2_filler.not_(nodrc_drw) + count = metal2_filler.count() + deck.info(f"metal2_filler has {count} polygons") + polygons_count += count + + metal2_slit = get_polygons(10, 24) + metal2_slit = metal2_slit.not_(nodrc_drw) + count = metal2_slit.count() + deck.info(f"metal2_slit has {count} polygons") + polygons_count += count + + # metal2 org + metal2 = metal2_drw.join(metal2_filler).not_(metal2_slit) + count = metal2.count() + deck.info(f"metal2 has {count} polygons") + + metal2_pin = get_polygons(10, 2) + metal2_pin = metal2_pin.not_(nodrc_drw) + count = metal2_pin.count() + deck.info(f"metal2_pin has {count} polygons") + polygons_count += count + + metal2_mask = get_polygons(10, 20) + metal2_mask = metal2_mask.not_(nodrc_drw) + count = metal2_mask.count() + deck.info(f"metal2_mask has {count} polygons") + polygons_count += count + + metal2_nofill = get_polygons(10, 23) + metal2_nofill = metal2_nofill.not_(nodrc_drw) + count = metal2_nofill.count() + deck.info(f"metal2_nofill has {count} polygons") + polygons_count += count + + metal2_text = deck.labels(10, 25) + metal2_text = metal2_text.not_(nodrc_drw) + count = metal2_text.count() + deck.info(f"metal2_text has {count} polygons") + polygons_count += count + + metal2_OPC = get_polygons(10, 26) + metal2_OPC = metal2_OPC.not_(nodrc_drw) + count = metal2_OPC.count() + deck.info(f"metal2_OPC has {count} polygons") + polygons_count += count + + metal2_noqrc = get_polygons(10, 28) + metal2_noqrc = metal2_noqrc.not_(nodrc_drw) + count = metal2_noqrc.count() + deck.info(f"metal2_noqrc has {count} polygons") + polygons_count += count + + metal2_res = get_polygons(10, 29) + metal2_res = metal2_res.not_(nodrc_drw) + count = metal2_res.count() + deck.info(f"metal2_res has {count} polygons") + polygons_count += count + + metal2_iprobe = get_polygons(10, 33) + metal2_iprobe = metal2_iprobe.not_(nodrc_drw) + count = metal2_iprobe.count() + deck.info(f"metal2_iprobe has {count} polygons") + polygons_count += count + + metal2_diffprb = get_polygons(10, 34) + metal2_diffprb = metal2_diffprb.not_(nodrc_drw) + count = metal2_diffprb.count() + deck.info(f"metal2_diffprb has {count} polygons") + polygons_count += count + + baspoly_drw = get_polygons(13, 0) + baspoly_drw = baspoly_drw.not_(nodrc_drw) + count = baspoly_drw.count() + deck.info(f"baspoly_drw has {count} polygons") + polygons_count += count + + baspoly_pin = get_polygons(13, 2) + baspoly_pin = baspoly_pin.not_(nodrc_drw) + count = baspoly_pin.count() + deck.info(f"baspoly_pin has {count} polygons") + polygons_count += count + + psd_drw = get_polygons(14, 0) + psd_drw = psd_drw.not_(nodrc_drw) + count = psd_drw.count() + deck.info(f"psd_drw has {count} polygons") + polygons_count += count + + nldb_drw = get_polygons(15, 0) + nldb_drw = nldb_drw.not_(nodrc_drw) + count = nldb_drw.count() + deck.info(f"nldb_drw has {count} polygons") + polygons_count += count + + digibnd_drw = get_polygons(16, 0) + digibnd_drw = digibnd_drw.not_(nodrc_drw) + count = digibnd_drw.count() + deck.info(f"digibnd_drw has {count} polygons") + polygons_count += count + + via1_drw = get_polygons(19, 0) + via1_drw = via1_drw.not_(nodrc_drw) + count = via1_drw.count() + deck.info(f"via1_drw has {count} polygons") + polygons_count += count + + backmetal1_drw = get_polygons(20, 0) + backmetal1_drw = backmetal1_drw.not_(nodrc_drw) + count = backmetal1_drw.count() + deck.info(f"backmetal1_drw has {count} polygons") + polygons_count += count + + backmetal1_pin = get_polygons(20, 2) + backmetal1_pin = backmetal1_pin.not_(nodrc_drw) + count = backmetal1_pin.count() + deck.info(f"backmetal1_pin has {count} polygons") + polygons_count += count + + backmetal1_mask = get_polygons(20, 20) + backmetal1_mask = backmetal1_mask.not_(nodrc_drw) + count = backmetal1_mask.count() + deck.info(f"backmetal1_mask has {count} polygons") + polygons_count += count + + backmetal1_filler = get_polygons(20, 22) + backmetal1_filler = backmetal1_filler.not_(nodrc_drw) + count = backmetal1_filler.count() + deck.info(f"backmetal1_filler has {count} polygons") + polygons_count += count + + backmetal1_nofill = get_polygons(20, 23) + backmetal1_nofill = backmetal1_nofill.not_(nodrc_drw) + count = backmetal1_nofill.count() + deck.info(f"backmetal1_nofill has {count} polygons") + polygons_count += count + + backmetal1_slit = get_polygons(20, 24) + backmetal1_slit = backmetal1_slit.not_(nodrc_drw) + count = backmetal1_slit.count() + deck.info(f"backmetal1_slit has {count} polygons") + polygons_count += count + + backmetal1_text = deck.labels(20, 25) + backmetal1_text = backmetal1_text.not_(nodrc_drw) + count = backmetal1_text.count() + deck.info(f"backmetal1_text has {count} polygons") + polygons_count += count + + backmetal1_OPC = get_polygons(20, 26) + backmetal1_OPC = backmetal1_OPC.not_(nodrc_drw) + count = backmetal1_OPC.count() + deck.info(f"backmetal1_OPC has {count} polygons") + polygons_count += count + + backmetal1_noqrc = get_polygons(20, 28) + backmetal1_noqrc = backmetal1_noqrc.not_(nodrc_drw) + count = backmetal1_noqrc.count() + deck.info(f"backmetal1_noqrc has {count} polygons") + polygons_count += count + + backmetal1_res = get_polygons(20, 29) + backmetal1_res = backmetal1_res.not_(nodrc_drw) + count = backmetal1_res.count() + deck.info(f"backmetal1_res has {count} polygons") + polygons_count += count + + backmetal1_iprobe = get_polygons(20, 33) + backmetal1_iprobe = backmetal1_iprobe.not_(nodrc_drw) + count = backmetal1_iprobe.count() + deck.info(f"backmetal1_iprobe has {count} polygons") + polygons_count += count + + backmetal1_diffprb = get_polygons(20, 34) + backmetal1_diffprb = backmetal1_diffprb.not_(nodrc_drw) + count = backmetal1_diffprb.count() + deck.info(f"backmetal1_diffprb has {count} polygons") + polygons_count += count + + backpassiv_drw = get_polygons(23, 0) + backpassiv_drw = backpassiv_drw.not_(nodrc_drw) + count = backpassiv_drw.count() + deck.info(f"backpassiv_drw has {count} polygons") + polygons_count += count + + res_drw = get_polygons(24, 0) + res_drw = res_drw.not_(nodrc_drw) + count = res_drw.count() + deck.info(f"res_drw has {count} polygons") + polygons_count += count + + sram_drw = get_polygons(25, 0) + sram_drw = sram_drw.not_(nodrc_drw) + count = sram_drw.count() + deck.info(f"sram_drw has {count} polygons") + polygons_count += count + + trans_drw = get_polygons(26, 0) + trans_drw = trans_drw.not_(nodrc_drw) + count = trans_drw.count() + deck.info(f"trans_drw has {count} polygons") + polygons_count += count + + ind_drw = get_polygons(27, 0) + ind_drw = ind_drw.not_(nodrc_drw) + count = ind_drw.count() + deck.info(f"ind_drw has {count} polygons") + polygons_count += count + + ind_pin = get_polygons(27, 2) + ind_pin = ind_pin.not_(nodrc_drw) + count = ind_pin.count() + deck.info(f"ind_pin has {count} polygons") + polygons_count += count + + ind_text = deck.labels(27, 25) + ind_text = ind_text.not_(nodrc_drw) + count = ind_text.count() + deck.info(f"ind_text has {count} polygons") + polygons_count += count + + salblock_drw = get_polygons(28, 0) + salblock_drw = salblock_drw.not_(nodrc_drw) + count = salblock_drw.count() + deck.info(f"salblock_drw has {count} polygons") + polygons_count += count + + via2_drw = get_polygons(29, 0) + via2_drw = via2_drw.not_(nodrc_drw) + count = via2_drw.count() + deck.info(f"via2_drw has {count} polygons") + polygons_count += count + + metal3_drw = get_polygons(30, 0) + metal3_drw = metal3_drw.not_(nodrc_drw) + count = metal3_drw.count() + deck.info(f"metal3_drw has {count} polygons") + polygons_count += count + + metal3_filler = get_polygons(30, 22) + metal3_filler = metal3_filler.not_(nodrc_drw) + count = metal3_filler.count() + deck.info(f"metal3_filler has {count} polygons") + polygons_count += count + + metal3_slit = get_polygons(30, 24) + metal3_slit = metal3_slit.not_(nodrc_drw) + count = metal3_slit.count() + deck.info(f"metal3_slit has {count} polygons") + polygons_count += count + + # metal3 org + metal3 = metal3_drw.join(metal3_filler).not_(metal3_slit) + count = metal3.count() + deck.info(f"metal3 has {count} polygons") + + metal3_pin = get_polygons(30, 2) + metal3_pin = metal3_pin.not_(nodrc_drw) + count = metal3_pin.count() + deck.info(f"metal3_pin has {count} polygons") + polygons_count += count + + metal3_mask = get_polygons(30, 20) + metal3_mask = metal3_mask.not_(nodrc_drw) + count = metal3_mask.count() + deck.info(f"metal3_mask has {count} polygons") + polygons_count += count + + metal3_nofill = get_polygons(30, 23) + metal3_nofill = metal3_nofill.not_(nodrc_drw) + count = metal3_nofill.count() + deck.info(f"metal3_nofill has {count} polygons") + polygons_count += count + + metal3_text = deck.labels(30, 25) + metal3_text = metal3_text.not_(nodrc_drw) + count = metal3_text.count() + deck.info(f"metal3_text has {count} polygons") + polygons_count += count + + metal3_OPC = get_polygons(30, 26) + metal3_OPC = metal3_OPC.not_(nodrc_drw) + count = metal3_OPC.count() + deck.info(f"metal3_OPC has {count} polygons") + polygons_count += count + + metal3_noqrc = get_polygons(30, 28) + metal3_noqrc = metal3_noqrc.not_(nodrc_drw) + count = metal3_noqrc.count() + deck.info(f"metal3_noqrc has {count} polygons") + polygons_count += count + + metal3_res = get_polygons(30, 29) + metal3_res = metal3_res.not_(nodrc_drw) + count = metal3_res.count() + deck.info(f"metal3_res has {count} polygons") + polygons_count += count + + metal3_iprobe = get_polygons(30, 33) + metal3_iprobe = metal3_iprobe.not_(nodrc_drw) + count = metal3_iprobe.count() + deck.info(f"metal3_iprobe has {count} polygons") + polygons_count += count + + metal3_diffprb = get_polygons(30, 34) + metal3_diffprb = metal3_diffprb.not_(nodrc_drw) + count = metal3_diffprb.count() + deck.info(f"metal3_diffprb has {count} polygons") + polygons_count += count + + nwell_drw = get_polygons(31, 0) + nwell_drw = nwell_drw.not_(nodrc_drw) + count = nwell_drw.count() + deck.info(f"nwell_drw has {count} polygons") + polygons_count += count + + nwell_pin = get_polygons(31, 2) + nwell_pin = nwell_pin.not_(nodrc_drw) + count = nwell_pin.count() + deck.info(f"nwell_pin has {count} polygons") + polygons_count += count + + nbulay_drw = get_polygons(32, 0) + nbulay_drw = nbulay_drw.not_(nodrc_drw) + count = nbulay_drw.count() + deck.info(f"nbulay_drw has {count} polygons") + polygons_count += count + + nbulay_pin = get_polygons(32, 2) + nbulay_pin = nbulay_pin.not_(nodrc_drw) + count = nbulay_pin.count() + deck.info(f"nbulay_pin has {count} polygons") + polygons_count += count + + nbulay_block = get_polygons(32, 21) + nbulay_block = nbulay_block.not_(nodrc_drw) + count = nbulay_block.count() + deck.info(f"nbulay_block has {count} polygons") + polygons_count += count + + emwind_drw = get_polygons(33, 0) + emwind_drw = emwind_drw.not_(nodrc_drw) + count = emwind_drw.count() + deck.info(f"emwind_drw has {count} polygons") + polygons_count += count + + emwind_OPC = get_polygons(33, 26) + emwind_OPC = emwind_OPC.not_(nodrc_drw) + count = emwind_OPC.count() + deck.info(f"emwind_OPC has {count} polygons") + polygons_count += count + + deepco_drw = get_polygons(35, 0) + deepco_drw = deepco_drw.not_(nodrc_drw) + count = deepco_drw.count() + deck.info(f"deepco_drw has {count} polygons") + polygons_count += count + + mim_drw = get_polygons(36, 0) + mim_drw = mim_drw.not_(nodrc_drw) + count = mim_drw.count() + deck.info(f"mim_drw has {count} polygons") + polygons_count += count + + edgeseal_drw = get_polygons(39, 0) + edgeseal_drw = edgeseal_drw.not_(nodrc_drw) + count = edgeseal_drw.count() + deck.info(f"edgeseal_drw has {count} polygons") + polygons_count += count + + substrate_drw = get_polygons(40, 0) + substrate_drw = substrate_drw.not_(nodrc_drw) + count = substrate_drw.count() + deck.info(f"substrate_drw has {count} polygons") + polygons_count += count + + substrate_text = deck.labels(40, 25) + substrate_text = substrate_text.not_(nodrc_drw) + count = substrate_text.count() + deck.info(f"substrate_text has {count} polygons") + polygons_count += count + + dfpad_drw = get_polygons(41, 0) + dfpad_drw = dfpad_drw.not_(nodrc_drw) + count = dfpad_drw.count() + deck.info(f"dfpad_drw has {count} polygons") + polygons_count += count + + dfpad_pillar = get_polygons(41, 35) + dfpad_pillar = dfpad_pillar.not_(nodrc_drw) + count = dfpad_pillar.count() + deck.info(f"dfpad_pillar has {count} polygons") + polygons_count += count + + dfpad_sbump = get_polygons(41, 36) + dfpad_sbump = dfpad_sbump.not_(nodrc_drw) + count = dfpad_sbump.count() + deck.info(f"dfpad_sbump has {count} polygons") + polygons_count += count + + thickgateox_drw = get_polygons(44, 0) + thickgateox_drw = thickgateox_drw.not_(nodrc_drw) + count = thickgateox_drw.count() + deck.info(f"thickgateox_drw has {count} polygons") + polygons_count += count + + pldb_drw = get_polygons(45, 0) + pldb_drw = pldb_drw.not_(nodrc_drw) + count = pldb_drw.count() + deck.info(f"pldb_drw has {count} polygons") + polygons_count += count + + pwell_drw = get_polygons(46, 0) + pwell_drw = pwell_drw.not_(nodrc_drw) + count = pwell_drw.count() + deck.info(f"pwell_drw has {count} polygons") + polygons_count += count + + pwell_pin = get_polygons(46, 2) + pwell_pin = pwell_pin.not_(nodrc_drw) + count = pwell_pin.count() + deck.info(f"pwell_pin has {count} polygons") + polygons_count += count + + pwell_block = get_polygons(46, 21) + pwell_block = pwell_block.not_(nodrc_drw) + count = pwell_block.count() + deck.info(f"pwell_block has {count} polygons") + polygons_count += count + + ic_drw = get_polygons(48, 0) + ic_drw = ic_drw.not_(nodrc_drw) + count = ic_drw.count() + deck.info(f"ic_drw has {count} polygons") + polygons_count += count + + via3_drw = get_polygons(49, 0) + via3_drw = via3_drw.not_(nodrc_drw) + count = via3_drw.count() + deck.info(f"via3_drw has {count} polygons") + polygons_count += count + + metal4_drw = get_polygons(50, 0) + metal4_drw = metal4_drw.not_(nodrc_drw) + count = metal4_drw.count() + deck.info(f"metal4_drw has {count} polygons") + polygons_count += count + + metal4_filler = get_polygons(50, 22) + metal4_filler = metal4_filler.not_(nodrc_drw) + count = metal4_filler.count() + deck.info(f"metal4_filler has {count} polygons") + polygons_count += count + + metal4_slit = get_polygons(50, 24) + metal4_slit = metal4_slit.not_(nodrc_drw) + count = metal4_slit.count() + deck.info(f"metal4_slit has {count} polygons") + polygons_count += count + + # metal4 org + metal4 = metal4_drw.join(metal4_filler).not_(metal4_slit) + count = metal4.count() + deck.info(f"metal4 has {count} polygons") + + metal4_pin = get_polygons(50, 2) + metal4_pin = metal4_pin.not_(nodrc_drw) + count = metal4_pin.count() + deck.info(f"metal4_pin has {count} polygons") + polygons_count += count + + metal4_mask = get_polygons(50, 20) + metal4_mask = metal4_mask.not_(nodrc_drw) + count = metal4_mask.count() + deck.info(f"metal4_mask has {count} polygons") + polygons_count += count + + metal4_nofill = get_polygons(50, 23) + metal4_nofill = metal4_nofill.not_(nodrc_drw) + count = metal4_nofill.count() + deck.info(f"metal4_nofill has {count} polygons") + polygons_count += count + + metal4_text = deck.labels(50, 25) + metal4_text = metal4_text.not_(nodrc_drw) + count = metal4_text.count() + deck.info(f"metal4_text has {count} polygons") + polygons_count += count + + metal4_OPC = get_polygons(50, 26) + metal4_OPC = metal4_OPC.not_(nodrc_drw) + count = metal4_OPC.count() + deck.info(f"metal4_OPC has {count} polygons") + polygons_count += count + + metal4_noqrc = get_polygons(50, 28) + metal4_noqrc = metal4_noqrc.not_(nodrc_drw) + count = metal4_noqrc.count() + deck.info(f"metal4_noqrc has {count} polygons") + polygons_count += count + + metal4_res = get_polygons(50, 29) + metal4_res = metal4_res.not_(nodrc_drw) + count = metal4_res.count() + deck.info(f"metal4_res has {count} polygons") + polygons_count += count + + metal4_iprobe = get_polygons(50, 33) + metal4_iprobe = metal4_iprobe.not_(nodrc_drw) + count = metal4_iprobe.count() + deck.info(f"metal4_iprobe has {count} polygons") + polygons_count += count + + metal4_diffprb = get_polygons(50, 34) + metal4_diffprb = metal4_diffprb.not_(nodrc_drw) + count = metal4_diffprb.count() + deck.info(f"metal4_diffprb has {count} polygons") + polygons_count += count + + heattrans_drw = get_polygons(51, 0) + heattrans_drw = heattrans_drw.not_(nodrc_drw) + count = heattrans_drw.count() + deck.info(f"heattrans_drw has {count} polygons") + polygons_count += count + + heatres_drw = get_polygons(52, 0) + heatres_drw = heatres_drw.not_(nodrc_drw) + count = heatres_drw.count() + deck.info(f"heatres_drw has {count} polygons") + polygons_count += count + + fbe_drw = get_polygons(54, 0) + fbe_drw = fbe_drw.not_(nodrc_drw) + count = fbe_drw.count() + deck.info(f"fbe_drw has {count} polygons") + polygons_count += count + + empoly_drw = get_polygons(55, 0) + empoly_drw = empoly_drw.not_(nodrc_drw) + count = empoly_drw.count() + deck.info(f"empoly_drw has {count} polygons") + polygons_count += count + + digisub_drw = get_polygons(60, 0) + digisub_drw = digisub_drw.not_(nodrc_drw) + count = digisub_drw.count() + deck.info(f"digisub_drw has {count} polygons") + polygons_count += count + + text_drw = deck.labels(63, 0) + text_drw = text_drw.not_(nodrc_drw) + count = text_drw.count() + deck.info(f"text_drw has {count} polygons") + polygons_count += count + + via4_drw = get_polygons(66, 0) + via4_drw = via4_drw.not_(nodrc_drw) + count = via4_drw.count() + deck.info(f"via4_drw has {count} polygons") + polygons_count += count + + metal5_drw = get_polygons(67, 0) + metal5_drw = metal5_drw.not_(nodrc_drw) + count = metal5_drw.count() + deck.info(f"metal5_drw has {count} polygons") + polygons_count += count + + metal5_filler = get_polygons(67, 22) + metal5_filler = metal5_filler.not_(nodrc_drw) + count = metal5_filler.count() + deck.info(f"metal5_filler has {count} polygons") + polygons_count += count + + metal5_slit = get_polygons(67, 24) + metal5_slit = metal5_slit.not_(nodrc_drw) + count = metal5_slit.count() + deck.info(f"metal5_slit has {count} polygons") + polygons_count += count + + # metal5 org + metal5 = metal5_drw.join(metal5_filler).not_(metal5_slit) + count = metal5.count() + deck.info(f"metal5 has {count} polygons") + + metal5_pin = get_polygons(67, 2) + metal5_pin = metal5_pin.not_(nodrc_drw) + count = metal5_pin.count() + deck.info(f"metal5_pin has {count} polygons") + polygons_count += count + + metal5_mask = get_polygons(67, 20) + metal5_mask = metal5_mask.not_(nodrc_drw) + count = metal5_mask.count() + deck.info(f"metal5_mask has {count} polygons") + polygons_count += count + + metal5_nofill = get_polygons(67, 23) + metal5_nofill = metal5_nofill.not_(nodrc_drw) + count = metal5_nofill.count() + deck.info(f"metal5_nofill has {count} polygons") + polygons_count += count + + metal5_text = deck.labels(67, 25) + metal5_text = metal5_text.not_(nodrc_drw) + count = metal5_text.count() + deck.info(f"metal5_text has {count} polygons") + polygons_count += count + + metal5_OPC = get_polygons(67, 26) + metal5_OPC = metal5_OPC.not_(nodrc_drw) + count = metal5_OPC.count() + deck.info(f"metal5_OPC has {count} polygons") + polygons_count += count + + metal5_noqrc = get_polygons(67, 28) + metal5_noqrc = metal5_noqrc.not_(nodrc_drw) + count = metal5_noqrc.count() + deck.info(f"metal5_noqrc has {count} polygons") + polygons_count += count + + metal5_res = get_polygons(67, 29) + metal5_res = metal5_res.not_(nodrc_drw) + count = metal5_res.count() + deck.info(f"metal5_res has {count} polygons") + polygons_count += count + + metal5_iprobe = get_polygons(67, 33) + metal5_iprobe = metal5_iprobe.not_(nodrc_drw) + count = metal5_iprobe.count() + deck.info(f"metal5_iprobe has {count} polygons") + polygons_count += count + + metal5_diffprb = get_polygons(67, 34) + metal5_diffprb = metal5_diffprb.not_(nodrc_drw) + count = metal5_diffprb.count() + deck.info(f"metal5_diffprb has {count} polygons") + polygons_count += count + + radhard_drw = get_polygons(68, 0) + radhard_drw = radhard_drw.not_(nodrc_drw) + count = radhard_drw.count() + deck.info(f"radhard_drw has {count} polygons") + polygons_count += count + + memcap_drw = get_polygons(69, 0) + memcap_drw = memcap_drw.not_(nodrc_drw) + count = memcap_drw.count() + deck.info(f"memcap_drw has {count} polygons") + polygons_count += count + + varicap_drw = get_polygons(70, 0) + varicap_drw = varicap_drw.not_(nodrc_drw) + count = varicap_drw.count() + deck.info(f"varicap_drw has {count} polygons") + polygons_count += count + + intbondvia_drw = get_polygons(72, 0) + intbondvia_drw = intbondvia_drw.not_(nodrc_drw) + count = intbondvia_drw.count() + deck.info(f"intbondvia_drw has {count} polygons") + polygons_count += count + + intbondmet_drw = get_polygons(73, 0) + intbondmet_drw = intbondmet_drw.not_(nodrc_drw) + count = intbondmet_drw.count() + deck.info(f"intbondmet_drw has {count} polygons") + polygons_count += count + + devbondvia_drw = get_polygons(74, 0) + devbondvia_drw = devbondvia_drw.not_(nodrc_drw) + count = devbondvia_drw.count() + deck.info(f"devbondvia_drw has {count} polygons") + polygons_count += count + + devbondmet_drw = get_polygons(75, 0) + devbondmet_drw = devbondmet_drw.not_(nodrc_drw) + count = devbondmet_drw.count() + deck.info(f"devbondmet_drw has {count} polygons") + polygons_count += count + + devtrench_drw = get_polygons(76, 0) + devtrench_drw = devtrench_drw.not_(nodrc_drw) + count = devtrench_drw.count() + deck.info(f"devtrench_drw has {count} polygons") + polygons_count += count + + redist_drw = get_polygons(77, 0) + redist_drw = redist_drw.not_(nodrc_drw) + count = redist_drw.count() + deck.info(f"redist_drw has {count} polygons") + polygons_count += count + + graphbot_drw = get_polygons(78, 0) + graphbot_drw = graphbot_drw.not_(nodrc_drw) + count = graphbot_drw.count() + deck.info(f"graphbot_drw has {count} polygons") + polygons_count += count + + graphtop_drw = get_polygons(79, 0) + graphtop_drw = graphtop_drw.not_(nodrc_drw) + count = graphtop_drw.count() + deck.info(f"graphtop_drw has {count} polygons") + polygons_count += count + + antvia1_drw = get_polygons(83, 0) + antvia1_drw = antvia1_drw.not_(nodrc_drw) + count = antvia1_drw.count() + deck.info(f"antvia1_drw has {count} polygons") + polygons_count += count + + antmetal2_drw = get_polygons(84, 0) + antmetal2_drw = antmetal2_drw.not_(nodrc_drw) + count = antmetal2_drw.count() + deck.info(f"antmetal2_drw has {count} polygons") + polygons_count += count + + graphcont_drw = get_polygons(85, 0) + graphcont_drw = graphcont_drw.not_(nodrc_drw) + count = graphcont_drw.count() + deck.info(f"graphcont_drw has {count} polygons") + polygons_count += count + + siwg_drw = get_polygons(86, 0) + siwg_drw = siwg_drw.not_(nodrc_drw) + count = siwg_drw.count() + deck.info(f"siwg_drw has {count} polygons") + polygons_count += count + + siwg_filler = get_polygons(86, 22) + siwg_filler = siwg_filler.not_(nodrc_drw) + count = siwg_filler.count() + deck.info(f"siwg_filler has {count} polygons") + polygons_count += count + + siwg_nofill = get_polygons(86, 23) + siwg_nofill = siwg_nofill.not_(nodrc_drw) + count = siwg_nofill.count() + deck.info(f"siwg_nofill has {count} polygons") + polygons_count += count + + sigrating_drw = get_polygons(87, 0) + sigrating_drw = sigrating_drw.not_(nodrc_drw) + count = sigrating_drw.count() + deck.info(f"sigrating_drw has {count} polygons") + polygons_count += count + + singrating_drw = get_polygons(88, 0) + singrating_drw = singrating_drw.not_(nodrc_drw) + count = singrating_drw.count() + deck.info(f"singrating_drw has {count} polygons") + polygons_count += count + + graphpas_drw = get_polygons(89, 0) + graphpas_drw = graphpas_drw.not_(nodrc_drw) + count = graphpas_drw.count() + deck.info(f"graphpas_drw has {count} polygons") + polygons_count += count + + emwind3_drw = get_polygons(90, 0) + emwind3_drw = emwind3_drw.not_(nodrc_drw) + count = emwind3_drw.count() + deck.info(f"emwind3_drw has {count} polygons") + polygons_count += count + + emwihv3_drw = get_polygons(91, 0) + emwihv3_drw = emwihv3_drw.not_(nodrc_drw) + count = emwihv3_drw.count() + deck.info(f"emwihv3_drw has {count} polygons") + polygons_count += count + + redbulay_drw = get_polygons(92, 0) + redbulay_drw = redbulay_drw.not_(nodrc_drw) + count = redbulay_drw.count() + deck.info(f"redbulay_drw has {count} polygons") + polygons_count += count + + smos_drw = get_polygons(93, 0) + smos_drw = smos_drw.not_(nodrc_drw) + count = smos_drw.count() + deck.info(f"smos_drw has {count} polygons") + polygons_count += count + + graphpad_drw = get_polygons(97, 0) + graphpad_drw = graphpad_drw.not_(nodrc_drw) + count = graphpad_drw.count() + deck.info(f"graphpad_drw has {count} polygons") + polygons_count += count + + polimide_drw = get_polygons(98, 0) + polimide_drw = polimide_drw.not_(nodrc_drw) + count = polimide_drw.count() + deck.info(f"polimide_drw has {count} polygons") + polygons_count += count + + polimide_pin = get_polygons(98, 2) + polimide_pin = polimide_pin.not_(nodrc_drw) + count = polimide_pin.count() + deck.info(f"polimide_pin has {count} polygons") + polygons_count += count + + recog_drw = get_polygons(99, 0) + recog_drw = recog_drw.not_(nodrc_drw) + count = recog_drw.count() + deck.info(f"recog_drw has {count} polygons") + polygons_count += count + + recog_pin = get_polygons(99, 2) + recog_pin = recog_pin.not_(nodrc_drw) + count = recog_pin.count() + deck.info(f"recog_pin has {count} polygons") + polygons_count += count + + recog_esd = get_polygons(99, 30) + recog_esd = recog_esd.not_(nodrc_drw) + count = recog_esd.count() + deck.info(f"recog_esd has {count} polygons") + polygons_count += count + + recog_diode = get_polygons(99, 31) + recog_diode = recog_diode.not_(nodrc_drw) + count = recog_diode.count() + deck.info(f"recog_diode has {count} polygons") + polygons_count += count + + recog_tsv = get_polygons(99, 32) + recog_tsv = recog_tsv.not_(nodrc_drw) + count = recog_tsv.count() + deck.info(f"recog_tsv has {count} polygons") + polygons_count += count + + recog_iprobe = get_polygons(99, 33) + recog_iprobe = recog_iprobe.not_(nodrc_drw) + count = recog_iprobe.count() + deck.info(f"recog_iprobe has {count} polygons") + polygons_count += count + + recog_diffprb = get_polygons(99, 34) + recog_diffprb = recog_diffprb.not_(nodrc_drw) + count = recog_diffprb.count() + deck.info(f"recog_diffprb has {count} polygons") + polygons_count += count + + recog_pillar = get_polygons(99, 35) + recog_pillar = recog_pillar.not_(nodrc_drw) + count = recog_pillar.count() + deck.info(f"recog_pillar has {count} polygons") + polygons_count += count + + recog_sbump = get_polygons(99, 36) + recog_sbump = recog_sbump.not_(nodrc_drw) + count = recog_sbump.count() + deck.info(f"recog_sbump has {count} polygons") + polygons_count += count + + recog_otp = get_polygons(99, 37) + recog_otp = recog_otp.not_(nodrc_drw) + count = recog_otp.count() + deck.info(f"recog_otp has {count} polygons") + polygons_count += count + + recog_pdiode = get_polygons(99, 38) + recog_pdiode = recog_pdiode.not_(nodrc_drw) + count = recog_pdiode.count() + deck.info(f"recog_pdiode has {count} polygons") + polygons_count += count + + recog_mom = get_polygons(99, 39) + recog_mom = recog_mom.not_(nodrc_drw) + count = recog_mom.count() + deck.info(f"recog_mom has {count} polygons") + polygons_count += count + + recog_pcm = get_polygons(99, 100) + recog_pcm = recog_pcm.not_(nodrc_drw) + count = recog_pcm.count() + deck.info(f"recog_pcm has {count} polygons") + polygons_count += count + + colopen_drw = get_polygons(101, 0) + colopen_drw = colopen_drw.not_(nodrc_drw) + count = colopen_drw.count() + deck.info(f"colopen_drw has {count} polygons") + polygons_count += count + + graphmetal1_drw = get_polygons(109, 0) + graphmetal1_drw = graphmetal1_drw.not_(nodrc_drw) + count = graphmetal1_drw.count() + deck.info(f"graphmetal1_drw has {count} polygons") + polygons_count += count + + graphmetal1_filler = get_polygons(109, 22) + graphmetal1_filler = graphmetal1_filler.not_(nodrc_drw) + count = graphmetal1_filler.count() + deck.info(f"graphmetal1_filler has {count} polygons") + polygons_count += count + + graphmetal1_nofill = get_polygons(109, 23) + graphmetal1_nofill = graphmetal1_nofill.not_(nodrc_drw) + count = graphmetal1_nofill.count() + deck.info(f"graphmetal1_nofill has {count} polygons") + polygons_count += count + + graphmetal1_slit = get_polygons(109, 24) + graphmetal1_slit = graphmetal1_slit.not_(nodrc_drw) + count = graphmetal1_slit.count() + deck.info(f"graphmetal1_slit has {count} polygons") + polygons_count += count + + graphmetal1_OPC = get_polygons(109, 26) + graphmetal1_OPC = graphmetal1_OPC.not_(nodrc_drw) + count = graphmetal1_OPC.count() + deck.info(f"graphmetal1_OPC has {count} polygons") + polygons_count += count + + graphmet1l_drw = get_polygons(110, 0) + graphmet1l_drw = graphmet1l_drw.not_(nodrc_drw) + count = graphmet1l_drw.count() + deck.info(f"graphmet1l_drw has {count} polygons") + polygons_count += count + + graphmet1l_filler = get_polygons(110, 22) + graphmet1l_filler = graphmet1l_filler.not_(nodrc_drw) + count = graphmet1l_filler.count() + deck.info(f"graphmet1l_filler has {count} polygons") + polygons_count += count + + graphmet1l_nofill = get_polygons(110, 23) + graphmet1l_nofill = graphmet1l_nofill.not_(nodrc_drw) + count = graphmet1l_nofill.count() + deck.info(f"graphmet1l_nofill has {count} polygons") + polygons_count += count + + graphmet1l_slit = get_polygons(110, 24) + graphmet1l_slit = graphmet1l_slit.not_(nodrc_drw) + count = graphmet1l_slit.count() + deck.info(f"graphmet1l_slit has {count} polygons") + polygons_count += count + + graphmet1l_OPC = get_polygons(110, 26) + graphmet1l_OPC = graphmet1l_OPC.not_(nodrc_drw) + count = graphmet1l_OPC.count() + deck.info(f"graphmet1l_OPC has {count} polygons") + polygons_count += count + + extblock_drw = get_polygons(111, 0) + extblock_drw = extblock_drw.not_(nodrc_drw) + count = extblock_drw.count() + deck.info(f"extblock_drw has {count} polygons") + polygons_count += count + + nldd_drw = get_polygons(112, 0) + nldd_drw = nldd_drw.not_(nodrc_drw) + count = nldd_drw.count() + deck.info(f"nldd_drw has {count} polygons") + polygons_count += count + + pldd_drw = get_polygons(113, 0) + pldd_drw = pldd_drw.not_(nodrc_drw) + count = pldd_drw.count() + deck.info(f"pldd_drw has {count} polygons") + polygons_count += count + + next_drw = get_polygons(114, 0) + next_drw = next_drw.not_(nodrc_drw) + count = next_drw.count() + deck.info(f"next_drw has {count} polygons") + polygons_count += count + + pext_drw = get_polygons(115, 0) + pext_drw = pext_drw.not_(nodrc_drw) + count = pext_drw.count() + deck.info(f"pext_drw has {count} polygons") + polygons_count += count + + nexthv_drw = get_polygons(116, 0) + nexthv_drw = nexthv_drw.not_(nodrc_drw) + count = nexthv_drw.count() + deck.info(f"nexthv_drw has {count} polygons") + polygons_count += count + + pexthv_drw = get_polygons(117, 0) + pexthv_drw = pexthv_drw.not_(nodrc_drw) + count = pexthv_drw.count() + deck.info(f"pexthv_drw has {count} polygons") + polygons_count += count + + graphgate_drw = get_polygons(118, 0) + graphgate_drw = graphgate_drw.not_(nodrc_drw) + count = graphgate_drw.count() + deck.info(f"graphgate_drw has {count} polygons") + polygons_count += count + + sinwg_drw = get_polygons(119, 0) + sinwg_drw = sinwg_drw.not_(nodrc_drw) + count = sinwg_drw.count() + deck.info(f"sinwg_drw has {count} polygons") + polygons_count += count + + sinwg_filler = get_polygons(119, 22) + sinwg_filler = sinwg_filler.not_(nodrc_drw) + count = sinwg_filler.count() + deck.info(f"sinwg_filler has {count} polygons") + polygons_count += count + + sinwg_nofill = get_polygons(119, 23) + sinwg_nofill = sinwg_nofill.not_(nodrc_drw) + count = sinwg_nofill.count() + deck.info(f"sinwg_nofill has {count} polygons") + polygons_count += count + + mempad_drw = get_polygons(124, 0) + mempad_drw = mempad_drw.not_(nodrc_drw) + count = mempad_drw.count() + deck.info(f"mempad_drw has {count} polygons") + polygons_count += count + + topvia1_drw = get_polygons(125, 0) + topvia1_drw = topvia1_drw.not_(nodrc_drw) + count = topvia1_drw.count() + deck.info(f"topvia1_drw has {count} polygons") + polygons_count += count + + topmetal1_drw = get_polygons(126, 0) + topmetal1_drw = topmetal1_drw.not_(nodrc_drw) + count = topmetal1_drw.count() + deck.info(f"topmetal1_drw has {count} polygons") + polygons_count += count + + topmetal1_filler = get_polygons(126, 22) + topmetal1_filler = topmetal1_filler.not_(nodrc_drw) + count = topmetal1_filler.count() + deck.info(f"topmetal1_filler has {count} polygons") + polygons_count += count + + topmetal1_slit = get_polygons(126, 24) + topmetal1_slit = topmetal1_slit.not_(nodrc_drw) + count = topmetal1_slit.count() + deck.info(f"topmetal1_slit has {count} polygons") + polygons_count += count + + # topmetal1 org + topmetal1 = topmetal1_drw.join(topmetal1_filler).not_(topmetal1_slit) + count = topmetal1.count() + deck.info(f"topmetal1 has {count} polygons") + + topmetal1_pin = get_polygons(126, 2) + topmetal1_pin = topmetal1_pin.not_(nodrc_drw) + count = topmetal1_pin.count() + deck.info(f"topmetal1_pin has {count} polygons") + polygons_count += count + + topmetal1_mask = get_polygons(126, 20) + topmetal1_mask = topmetal1_mask.not_(nodrc_drw) + count = topmetal1_mask.count() + deck.info(f"topmetal1_mask has {count} polygons") + polygons_count += count + + topmetal1_nofill = get_polygons(126, 23) + topmetal1_nofill = topmetal1_nofill.not_(nodrc_drw) + count = topmetal1_nofill.count() + deck.info(f"topmetal1_nofill has {count} polygons") + polygons_count += count + + topmetal1_text = deck.labels(126, 25) + topmetal1_text = topmetal1_text.not_(nodrc_drw) + count = topmetal1_text.count() + deck.info(f"topmetal1_text has {count} polygons") + polygons_count += count + + topmetal1_noqrc = get_polygons(126, 28) + topmetal1_noqrc = topmetal1_noqrc.not_(nodrc_drw) + count = topmetal1_noqrc.count() + deck.info(f"topmetal1_noqrc has {count} polygons") + polygons_count += count + + topmetal1_res = get_polygons(126, 29) + topmetal1_res = topmetal1_res.not_(nodrc_drw) + count = topmetal1_res.count() + deck.info(f"topmetal1_res has {count} polygons") + polygons_count += count + + topmetal1_iprobe = get_polygons(126, 33) + topmetal1_iprobe = topmetal1_iprobe.not_(nodrc_drw) + count = topmetal1_iprobe.count() + deck.info(f"topmetal1_iprobe has {count} polygons") + polygons_count += count + + topmetal1_diffprb = get_polygons(126, 34) + topmetal1_diffprb = topmetal1_diffprb.not_(nodrc_drw) + count = topmetal1_diffprb.count() + deck.info(f"topmetal1_diffprb has {count} polygons") + polygons_count += count + + inldpwl_drw = get_polygons(127, 0) + inldpwl_drw = inldpwl_drw.not_(nodrc_drw) + count = inldpwl_drw.count() + deck.info(f"inldpwl_drw has {count} polygons") + polygons_count += count + + polyres_drw = get_polygons(128, 0) + polyres_drw = polyres_drw.not_(nodrc_drw) + count = polyres_drw.count() + deck.info(f"polyres_drw has {count} polygons") + polygons_count += count + + polyres_pin = get_polygons(128, 2) + polyres_pin = polyres_pin.not_(nodrc_drw) + count = polyres_pin.count() + deck.info(f"polyres_pin has {count} polygons") + polygons_count += count + + vmim_drw = get_polygons(129, 0) + vmim_drw = vmim_drw.not_(nodrc_drw) + count = vmim_drw.count() + deck.info(f"vmim_drw has {count} polygons") + polygons_count += count + + nbulaycut_drw = get_polygons(131, 0) + nbulaycut_drw = nbulaycut_drw.not_(nodrc_drw) + count = nbulaycut_drw.count() + deck.info(f"nbulaycut_drw has {count} polygons") + polygons_count += count + + antmetal1_drw = get_polygons(132, 0) + antmetal1_drw = antmetal1_drw.not_(nodrc_drw) + count = antmetal1_drw.count() + deck.info(f"antmetal1_drw has {count} polygons") + polygons_count += count + + topvia2_drw = get_polygons(133, 0) + topvia2_drw = topvia2_drw.not_(nodrc_drw) + count = topvia2_drw.count() + deck.info(f"topvia2_drw has {count} polygons") + polygons_count += count + + topmetal2_drw = get_polygons(134, 0) + topmetal2_drw = topmetal2_drw.not_(nodrc_drw) + count = topmetal2_drw.count() + deck.info(f"topmetal2_drw has {count} polygons") + polygons_count += count + + topmetal2_filler = get_polygons(134, 22) + topmetal2_filler = topmetal2_filler.not_(nodrc_drw) + count = topmetal2_filler.count() + deck.info(f"topmetal2_filler has {count} polygons") + polygons_count += count + + topmetal2_slit = get_polygons(134, 24) + topmetal2_slit = topmetal2_slit.not_(nodrc_drw) + count = topmetal2_slit.count() + deck.info(f"topmetal2_slit has {count} polygons") + polygons_count += count + + # topmetal2 org + topmetal2 = topmetal2_drw.join(topmetal2_filler).not_(topmetal2_slit) + count = topmetal2.count() + deck.info(f"topmetal2 has {count} polygons") + + topmetal2_pin = get_polygons(134, 2) + topmetal2_pin = topmetal2_pin.not_(nodrc_drw) + count = topmetal2_pin.count() + deck.info(f"topmetal2_pin has {count} polygons") + polygons_count += count + + topmetal2_mask = get_polygons(134, 20) + topmetal2_mask = topmetal2_mask.not_(nodrc_drw) + count = topmetal2_mask.count() + deck.info(f"topmetal2_mask has {count} polygons") + polygons_count += count + + topmetal2_nofill = get_polygons(134, 23) + topmetal2_nofill = topmetal2_nofill.not_(nodrc_drw) + count = topmetal2_nofill.count() + deck.info(f"topmetal2_nofill has {count} polygons") + polygons_count += count + + topmetal2_text = deck.labels(134, 25) + topmetal2_text = topmetal2_text.not_(nodrc_drw) + count = topmetal2_text.count() + deck.info(f"topmetal2_text has {count} polygons") + polygons_count += count + + topmetal2_noqrc = get_polygons(134, 28) + topmetal2_noqrc = topmetal2_noqrc.not_(nodrc_drw) + count = topmetal2_noqrc.count() + deck.info(f"topmetal2_noqrc has {count} polygons") + polygons_count += count + + topmetal2_res = get_polygons(134, 29) + topmetal2_res = topmetal2_res.not_(nodrc_drw) + count = topmetal2_res.count() + deck.info(f"topmetal2_res has {count} polygons") + polygons_count += count + + topmetal2_iprobe = get_polygons(134, 33) + topmetal2_iprobe = topmetal2_iprobe.not_(nodrc_drw) + count = topmetal2_iprobe.count() + deck.info(f"topmetal2_iprobe has {count} polygons") + polygons_count += count + + topmetal2_diffprb = get_polygons(134, 34) + topmetal2_diffprb = topmetal2_diffprb.not_(nodrc_drw) + count = topmetal2_diffprb.count() + deck.info(f"topmetal2_diffprb has {count} polygons") + polygons_count += count + + snsring_drw = get_polygons(135, 0) + snsring_drw = snsring_drw.not_(nodrc_drw) + count = snsring_drw.count() + deck.info(f"snsring_drw has {count} polygons") + polygons_count += count + + sensor_drw = get_polygons(136, 0) + sensor_drw = sensor_drw.not_(nodrc_drw) + count = sensor_drw.count() + deck.info(f"sensor_drw has {count} polygons") + polygons_count += count + + snsarms_drw = get_polygons(137, 0) + snsarms_drw = snsarms_drw.not_(nodrc_drw) + count = snsarms_drw.count() + deck.info(f"snsarms_drw has {count} polygons") + polygons_count += count + + snscmosvia_drw = get_polygons(138, 0) + snscmosvia_drw = snscmosvia_drw.not_(nodrc_drw) + count = snscmosvia_drw.count() + deck.info(f"snscmosvia_drw has {count} polygons") + polygons_count += count + + colwind_drw = get_polygons(139, 0) + colwind_drw = colwind_drw.not_(nodrc_drw) + count = colwind_drw.count() + deck.info(f"colwind_drw has {count} polygons") + polygons_count += count + + flm_drw = get_polygons(142, 0) + flm_drw = flm_drw.not_(nodrc_drw) + count = flm_drw.count() + deck.info(f"flm_drw has {count} polygons") + polygons_count += count + + hafniumox_drw = get_polygons(143, 0) + hafniumox_drw = hafniumox_drw.not_(nodrc_drw) + count = hafniumox_drw.count() + deck.info(f"hafniumox_drw has {count} polygons") + polygons_count += count + + memvia_drw = get_polygons(145, 0) + memvia_drw = memvia_drw.not_(nodrc_drw) + count = memvia_drw.count() + deck.info(f"memvia_drw has {count} polygons") + polygons_count += count + + thinfilmres_drw = get_polygons(146, 0) + thinfilmres_drw = thinfilmres_drw.not_(nodrc_drw) + count = thinfilmres_drw.count() + deck.info(f"thinfilmres_drw has {count} polygons") + polygons_count += count + + rfmem_drw = get_polygons(147, 0) + rfmem_drw = rfmem_drw.not_(nodrc_drw) + count = rfmem_drw.count() + deck.info(f"rfmem_drw has {count} polygons") + polygons_count += count + + norcx_drw = get_polygons(148, 0) + norcx_drw = norcx_drw.not_(nodrc_drw) + count = norcx_drw.count() + deck.info(f"norcx_drw has {count} polygons") + polygons_count += count + + norcx_m2m3 = get_polygons(148, 41) + norcx_m2m3 = norcx_m2m3.not_(nodrc_drw) + count = norcx_m2m3.count() + deck.info(f"norcx_m2m3 has {count} polygons") + polygons_count += count + + norcx_m2m4 = get_polygons(148, 42) + norcx_m2m4 = norcx_m2m4.not_(nodrc_drw) + count = norcx_m2m4.count() + deck.info(f"norcx_m2m4 has {count} polygons") + polygons_count += count + + norcx_m2m5 = get_polygons(148, 43) + norcx_m2m5 = norcx_m2m5.not_(nodrc_drw) + count = norcx_m2m5.count() + deck.info(f"norcx_m2m5 has {count} polygons") + polygons_count += count + + norcx_m2tm1 = get_polygons(148, 44) + norcx_m2tm1 = norcx_m2tm1.not_(nodrc_drw) + count = norcx_m2tm1.count() + deck.info(f"norcx_m2tm1 has {count} polygons") + polygons_count += count + + norcx_m2tm2 = get_polygons(148, 45) + norcx_m2tm2 = norcx_m2tm2.not_(nodrc_drw) + count = norcx_m2tm2.count() + deck.info(f"norcx_m2tm2 has {count} polygons") + polygons_count += count + + norcx_m3m4 = get_polygons(148, 46) + norcx_m3m4 = norcx_m3m4.not_(nodrc_drw) + count = norcx_m3m4.count() + deck.info(f"norcx_m3m4 has {count} polygons") + polygons_count += count + + norcx_m3m5 = get_polygons(148, 47) + norcx_m3m5 = norcx_m3m5.not_(nodrc_drw) + count = norcx_m3m5.count() + deck.info(f"norcx_m3m5 has {count} polygons") + polygons_count += count + + norcx_m3tm1 = get_polygons(148, 48) + norcx_m3tm1 = norcx_m3tm1.not_(nodrc_drw) + count = norcx_m3tm1.count() + deck.info(f"norcx_m3tm1 has {count} polygons") + polygons_count += count + + norcx_m3tm2 = get_polygons(148, 49) + norcx_m3tm2 = norcx_m3tm2.not_(nodrc_drw) + count = norcx_m3tm2.count() + deck.info(f"norcx_m3tm2 has {count} polygons") + polygons_count += count + + norcx_m4m5 = get_polygons(148, 50) + norcx_m4m5 = norcx_m4m5.not_(nodrc_drw) + count = norcx_m4m5.count() + deck.info(f"norcx_m4m5 has {count} polygons") + polygons_count += count + + norcx_m4tm1 = get_polygons(148, 51) + norcx_m4tm1 = norcx_m4tm1.not_(nodrc_drw) + count = norcx_m4tm1.count() + deck.info(f"norcx_m4tm1 has {count} polygons") + polygons_count += count + + norcx_m4tm2 = get_polygons(148, 52) + norcx_m4tm2 = norcx_m4tm2.not_(nodrc_drw) + count = norcx_m4tm2.count() + deck.info(f"norcx_m4tm2 has {count} polygons") + polygons_count += count + + norcx_m5tm1 = get_polygons(148, 53) + norcx_m5tm1 = norcx_m5tm1.not_(nodrc_drw) + count = norcx_m5tm1.count() + deck.info(f"norcx_m5tm1 has {count} polygons") + polygons_count += count + + norcx_m5tm2 = get_polygons(148, 54) + norcx_m5tm2 = norcx_m5tm2.not_(nodrc_drw) + count = norcx_m5tm2.count() + deck.info(f"norcx_m5tm2 has {count} polygons") + polygons_count += count + + norcx_tm1tm2 = get_polygons(148, 55) + norcx_tm1tm2 = norcx_tm1tm2.not_(nodrc_drw) + count = norcx_tm1tm2.count() + deck.info(f"norcx_tm1tm2 has {count} polygons") + polygons_count += count + + norcx_m1sub = get_polygons(148, 123) + norcx_m1sub = norcx_m1sub.not_(nodrc_drw) + count = norcx_m1sub.count() + deck.info(f"norcx_m1sub has {count} polygons") + polygons_count += count + + norcx_m2sub = get_polygons(148, 124) + norcx_m2sub = norcx_m2sub.not_(nodrc_drw) + count = norcx_m2sub.count() + deck.info(f"norcx_m2sub has {count} polygons") + polygons_count += count + + norcx_m3sub = get_polygons(148, 125) + norcx_m3sub = norcx_m3sub.not_(nodrc_drw) + count = norcx_m3sub.count() + deck.info(f"norcx_m3sub has {count} polygons") + polygons_count += count + + norcx_m4sub = get_polygons(148, 126) + norcx_m4sub = norcx_m4sub.not_(nodrc_drw) + count = norcx_m4sub.count() + deck.info(f"norcx_m4sub has {count} polygons") + polygons_count += count + + norcx_m5sub = get_polygons(148, 127) + norcx_m5sub = norcx_m5sub.not_(nodrc_drw) + count = norcx_m5sub.count() + deck.info(f"norcx_m5sub has {count} polygons") + polygons_count += count + + norcx_tm1sub = get_polygons(148, 300) + norcx_tm1sub = norcx_tm1sub.not_(nodrc_drw) + count = norcx_tm1sub.count() + deck.info(f"norcx_tm1sub has {count} polygons") + polygons_count += count + + norcx_tm2sub = get_polygons(148, 301) + norcx_tm2sub = norcx_tm2sub.not_(nodrc_drw) + count = norcx_tm2sub.count() + deck.info(f"norcx_tm2sub has {count} polygons") + polygons_count += count + + snsbotvia_drw = get_polygons(149, 0) + snsbotvia_drw = snsbotvia_drw.not_(nodrc_drw) + count = snsbotvia_drw.count() + deck.info(f"snsbotvia_drw has {count} polygons") + polygons_count += count + + snstopvia_drw = get_polygons(151, 0) + snstopvia_drw = snstopvia_drw.not_(nodrc_drw) + count = snstopvia_drw.count() + deck.info(f"snstopvia_drw has {count} polygons") + polygons_count += count + + deepvia_drw = get_polygons(152, 0) + deepvia_drw = deepvia_drw.not_(nodrc_drw) + count = deepvia_drw.count() + deck.info(f"deepvia_drw has {count} polygons") + polygons_count += count + + fgetch_drw = get_polygons(153, 0) + fgetch_drw = fgetch_drw.not_(nodrc_drw) + count = fgetch_drw.count() + deck.info(f"fgetch_drw has {count} polygons") + polygons_count += count + + ctrgat_drw = get_polygons(154, 0) + ctrgat_drw = ctrgat_drw.not_(nodrc_drw) + count = ctrgat_drw.count() + deck.info(f"ctrgat_drw has {count} polygons") + polygons_count += count + + fgimp_drw = get_polygons(155, 0) + fgimp_drw = fgimp_drw.not_(nodrc_drw) + count = fgimp_drw.count() + deck.info(f"fgimp_drw has {count} polygons") + polygons_count += count + + emwihv_drw = get_polygons(156, 0) + emwihv_drw = emwihv_drw.not_(nodrc_drw) + count = emwihv_drw.count() + deck.info(f"emwihv_drw has {count} polygons") + polygons_count += count + + lbe_drw = get_polygons(157, 0) + lbe_drw = lbe_drw.not_(nodrc_drw) + count = lbe_drw.count() + deck.info(f"lbe_drw has {count} polygons") + polygons_count += count + + alcustop_drw = get_polygons(159, 0) + alcustop_drw = alcustop_drw.not_(nodrc_drw) + count = alcustop_drw.count() + deck.info(f"alcustop_drw has {count} polygons") + polygons_count += count + + nometfiller_drw = get_polygons(160, 0) + nometfiller_drw = nometfiller_drw.not_(nodrc_drw) + count = nometfiller_drw.count() + deck.info(f"nometfiller_drw has {count} polygons") + polygons_count += count + + prboundary_drw = get_polygons(189, 0) + prboundary_drw = prboundary_drw.not_(nodrc_drw) + count = prboundary_drw.count() + deck.info(f"prboundary_drw has {count} polygons") + polygons_count += count + + exchange0_drw = get_polygons(190, 0) + exchange0_drw = exchange0_drw.not_(nodrc_drw) + count = exchange0_drw.count() + deck.info(f"exchange0_drw has {count} polygons") + polygons_count += count + + exchange0_pin = get_polygons(190, 2) + exchange0_pin = exchange0_pin.not_(nodrc_drw) + count = exchange0_pin.count() + deck.info(f"exchange0_pin has {count} polygons") + polygons_count += count + + exchange0_text = deck.labels(190, 25) + exchange0_text = exchange0_text.not_(nodrc_drw) + count = exchange0_text.count() + deck.info(f"exchange0_text has {count} polygons") + polygons_count += count + + exchange1_drw = get_polygons(191, 0) + exchange1_drw = exchange1_drw.not_(nodrc_drw) + count = exchange1_drw.count() + deck.info(f"exchange1_drw has {count} polygons") + polygons_count += count + + exchange1_pin = get_polygons(191, 2) + exchange1_pin = exchange1_pin.not_(nodrc_drw) + count = exchange1_pin.count() + deck.info(f"exchange1_pin has {count} polygons") + polygons_count += count + + exchange1_text = deck.labels(191, 25) + exchange1_text = exchange1_text.not_(nodrc_drw) + count = exchange1_text.count() + deck.info(f"exchange1_text has {count} polygons") + polygons_count += count + + exchange2_drw = get_polygons(192, 0) + exchange2_drw = exchange2_drw.not_(nodrc_drw) + count = exchange2_drw.count() + deck.info(f"exchange2_drw has {count} polygons") + polygons_count += count + + exchange2_pin = get_polygons(192, 2) + exchange2_pin = exchange2_pin.not_(nodrc_drw) + count = exchange2_pin.count() + deck.info(f"exchange2_pin has {count} polygons") + polygons_count += count + + exchange2_text = deck.labels(192, 25) + exchange2_text = exchange2_text.not_(nodrc_drw) + count = exchange2_text.count() + deck.info(f"exchange2_text has {count} polygons") + polygons_count += count + + exchange3_drw = get_polygons(193, 0) + exchange3_drw = exchange3_drw.not_(nodrc_drw) + count = exchange3_drw.count() + deck.info(f"exchange3_drw has {count} polygons") + polygons_count += count + + exchange3_pin = get_polygons(193, 2) + exchange3_pin = exchange3_pin.not_(nodrc_drw) + count = exchange3_pin.count() + deck.info(f"exchange3_pin has {count} polygons") + polygons_count += count + + exchange3_text = deck.labels(193, 25) + exchange3_text = exchange3_text.not_(nodrc_drw) + count = exchange3_text.count() + deck.info(f"exchange3_text has {count} polygons") + polygons_count += count + + exchange4_drw = get_polygons(194, 0) + exchange4_drw = exchange4_drw.not_(nodrc_drw) + count = exchange4_drw.count() + deck.info(f"exchange4_drw has {count} polygons") + polygons_count += count + + exchange4_pin = get_polygons(194, 2) + exchange4_pin = exchange4_pin.not_(nodrc_drw) + count = exchange4_pin.count() + deck.info(f"exchange4_pin has {count} polygons") + polygons_count += count + + exchange4_text = deck.labels(194, 25) + exchange4_text = exchange4_text.not_(nodrc_drw) + count = exchange4_text.count() + deck.info(f"exchange4_text has {count} polygons") + polygons_count += count + + isonwell_drw = get_polygons(257, 0) + isonwell_drw = isonwell_drw.not_(nodrc_drw) + count = isonwell_drw.count() + deck.info(f"isonwell_drw has {count} polygons") + polygons_count += count + + deck.info(f"Total no. of polygons in the design is {polygons_count}") + + # general_derivations.lvs + deck.info("Starting general LVS derivations") + + # === Global Layers === + # === CHIP === + CHIP = deck.extent("*") + + # === General Derivations === + # nwell + nwell_iso = nwell_drw.and_(nbulay_drw) + nwell_holes = nwell_drw.holes().not_(nwell_drw) + + # pwell + pwell_allowed = CHIP.not_(pwell_block) + digisub_gap = digisub_drw.not_(digisub_drw.sized(nm(-1))) + pwell = pwell_allowed.not_(nwell_drw).not_(digisub_gap) + + # General pwell + pwell_sub = pwell_allowed.not_(digisub_drw).not_( + nbulay_drw.interacting(nwell_holes) + ) + + # n & p activ + nactiv = activ.not_(psd_drw.join(nsd_block)) + pactiv = activ.and_(psd_drw) + + # res/cap exclusion + res_mk = polyres_drw.join(res_drw) + poly_con = gatpoly.not_(res_mk) + metal1_con = metal1.not_(metal1_res) + metal2_con = metal2.not_(metal2_res) + metal3_con = metal3.not_(metal3_res) + metal4_con = metal4.not_(metal4_res) + metal5_con = metal5.not_(metal5_res) + topmetal1_con = topmetal1.not_(topmetal1_res).not_(ind_drw) + topmetal2_con = topmetal2.not_(topmetal2_res).not_(ind_drw) + + # Gate FETs + tgate = gatpoly.and_(activ).not_(res_mk) + ngate = nactiv.and_(tgate) + pgate = pactiv.and_(tgate) + ngate_lv_base = ngate.not_(thickgateox_drw) + pgate_lv_base = pgate.not_(thickgateox_drw) + ngate_hv_base = ngate.and_(thickgateox_drw) + pgate_hv_base = pgate.and_(thickgateox_drw) + + # S/D FETs + nsd_fet = ( + nactiv.not_(nwell_drw).interacting(ngate).not_(ngate).not_interacting(res_mk) + ) + psd_fet = ( + pactiv.and_(nwell_drw).interacting(pgate).not_(pgate).not_interacting(res_mk) + ) + + # n1/p1 taps labels + well_patt = glob_to_case_insensitive_glob("well") + sub_patt = glob_to_case_insensitive_glob("sub!") + + ntap1_lbl = text_drw.texts(well_patt) + ntap1_mk = nwell_drw.interacting(ntap1_lbl) + + ptap1_lbl = text_drw.texts(sub_patt) + ptap1_mk = substrate_drw.and_(pwell).interacting(ptap1_lbl) + + # n & p taps (short connections) + ntap = nactiv.and_(nwell_drw).not_(recog_diode).not_(gatpoly).not_(ntap1_mk) + ptap = pactiv.and_(pwell).not_(ptap1_mk).not_(recog_diode).not_(gatpoly) + ptap_holes = ptap.holes() + ntap_holes = ntap.holes() + + # S/D (salicide) + nsd_sal = nsd_fet.not_(salblock_drw) + psd_sal = psd_fet.not_(salblock_drw) + + # n & p taps (salicide) + ntap_sal = ntap.not_(salblock_drw) + ptap_sal = ptap.not_(salblock_drw) + + # n/p SD abutted with n/p taps (salicide) + nsd_ptap_abutt = nsd_sal.edges().and_(ptap_sal.edges()).extended(i=nm(1), o=nm(1)) + psd_ntap_abutt = psd_sal.edges().and_(ntap_sal.edges()).extended(i=nm(1), o=nm(1)) + + # mos_derivations.lvs + deck.info("Starting MOSFET DERIVATIONS") + + mos_exclude = ( + pwell_block.join(nsd_drw) + .join(trans_drw) + .join(emwind_drw) + .join(emwihv_drw) + .join(salblock_drw) + .join(polyres_drw) + .join(extblock_drw) + .join(res_drw) + .join(activ_mask) + .join(recog_diode) + .join(recog_esd) + .join(ind_drw) + .join(ind_pin) + .join(ind_drw) + .join(substrate_drw) + .join(nsd_block) + ) + + # ==== General FETs & RF-FETs ===== + + rfnmos_exc = nwell_drw.join(psd_drw).join(mos_exclude) + rfpmos_exc = pwell.join(nwell_holes).join(mos_exclude) + + # Get case insensitive patterns for FETs + rfnmos_patt = glob_to_case_insensitive_glob("rfnmos") + rfnmoshv_patt = glob_to_case_insensitive_glob("rfnmosHV") + + rfpmos_patt = glob_to_case_insensitive_glob("rfpmos") + rfpmoshv_patt = glob_to_case_insensitive_glob("rfpmosHV") + + rfnmos_mk_gen = ptap.join(ptap_holes) + rfnmos_mk = rfnmos_mk_gen.interacting(text_drw.texts(rfnmos_patt)) + rfnmoshv_mk = rfnmos_mk_gen.interacting(text_drw.texts(rfnmoshv_patt)) + + rfpmos_mk_gen = ntap.join(ntap_holes) + rfpmos_mk = rfpmos_mk_gen.interacting(text_drw.texts(rfpmos_patt)) + rfpmoshv_mk = rfpmos_mk_gen.interacting(text_drw.texts(rfpmoshv_patt)) + + # ============== + # ---- NMOS ---- + # ============== + + deck.info("Starting NMOS DERIVATIONS") + + # for regular FETs + nmos_exc = rfnmos_exc.join(rfnmos_mk) + nmoshv_exc = rfnmos_exc.join(rfnmoshv_mk) + + # nmos - LV + ngate_lv = ngate_lv_base.not_(nmos_exc) + + # nmos - HV + ngate_hv = ngate_hv_base.not_(nmoshv_exc) + + # ============== + # ---- PMOS ---- + # ============== + + deck.info("Starting PMOS DERIVATIONS") + + # for regular FETs + pmos_exc = rfpmos_exc.join(rfpmos_mk) + pmoshv_exc = rfpmos_exc.join(rfpmoshv_mk) + + # pmos - LV + pgate_lv = pgate_lv_base.not_(pmos_exc) + + # pmos - HV + pgate_hv = pgate_hv_base.not_(pmoshv_exc) + + # rfmos_derivations.lvs + deck.info("Starting RF-MOSFET DERIVATIONS") + + # =============== + # --- RF-NMOS --- + # =============== + + deck.info("Starting RF-NMOS DERIVATIONS") + + # rfnmos - LV + rfngate_lv = ngate_lv_base.and_(rfnmos_mk).not_(rfnmos_exc) + + # rfnmos - HV + rfngate_hv = ngate_hv_base.and_(rfnmoshv_mk).not_(rfnmos_exc) + + # =============== + # --- RF-PMOS --- + # =============== + + deck.info("Starting RF-PMOS DERIVATIONS") + + # rfpmos - LV + rfpgate_lv = pgate_lv_base.and_(rfpmos_mk).not_(rfpmos_exc) + + # rfpmos - HV + rfpgate_hv = pgate_hv_base.and_(rfpmoshv_mk).not_(rfpmos_exc) + + # bjt_derivations.lvs + deck.info("Starting BJT DERIVATIONS") + + # ============= + # ---- NPN ---- + # ============= + + deck.info("Starting NPN-BJT DERIVATIONS") + + bjt_exclude = ( + gatpoly.join(pwell_block) + .join(nsd_drw) + .join(salblock_drw) + .join(polyres_drw) + .join(extblock_drw) + .join(res_drw) + .join(recog_diode) + .join(recog_esd) + .join(ind_drw) + .join(ind_pin) + .join(substrate_drw) + ) + + npn_exclude = nwell_drw.join(psd_drw).join(nbulay_drw).join(bjt_exclude) + + # ---------- General NPN ---------- + npn_mk = trans_drw.and_(pwell).and_(ptap_holes) + npn_c_exc = ( + emwind_drw.join(emwihv_drw).join(activ_mask).join(nsd_block).join(npn_exclude) + ) + npn_b_exc = emwind_drw.join(emwihv_drw).join(npn_exclude) + npn_sub = npn_mk.not_(npn_exclude) + npn_dev = activ.join(activ_mask).and_(npn_mk) + + # ---------- npn13G2 ---------- + # npn13G2 exclusion layers + npn13G2_e_exc = activ.join(emwihv_drw).join(npn_exclude) + npn13G2_b_exc = npn_b_exc.join(activ_mask) + + # npn13G2 nodes + npn13G2_e_ = ( + emwind_drw.and_(activ_mask).and_(nsd_block).and_(npn_mk).not_(npn13G2_e_exc) + ) + # npn13G2 is a fixed device (0.07um X 0.9um) + npn13G2_e_pin = ( + npn13G2_e_.with_bbox_min(um(0.07)).with_bbox_max(um(0.9)).with_area(um(0.063)) + ) + npn13G2_b_pin = nsd_block.and_(npn_mk).not_(npn13G2_b_exc) + npn13G2_c_pin = activ.and_(npn_mk).not_overlapping(npn_c_exc) + + npn13G2_dev = ( + npn_dev.join(nsd_block) + .extents() + .covering(npn13G2_e_pin) + .covering(npn13G2_b_pin) + .covering(npn13G2_c_pin) + ) + npn13G2_c = npn13G2_dev.sized(nm(-1)) + npn13G2_tc = npn13G2_dev.not_(npn13G2_c).interacting(npn13G2_c_pin) + npn13G2_b = npn13G2_dev.not_(npn13G2_c_pin) + npn13G2_tb = npn13G2_b.not_(npn13G2_e_pin).merged() + npn13G2_e = npn13G2_e_pin + npn13G2_te = npn13G2_e + + # ---------- npn13G2L ---------- + # npn13G2L exclusion layers + npn13G2l_e_exc = activ_mask.join(nsd_block).join(emwihv_drw).join(npn_exclude) + npn13G2l_b_exc = npn_b_exc.join(activ).join(nsd_block) + + # npn13G2L nodes + npn13G2l_e_ = emwind_drw.and_(activ).and_(npn_mk).not_(npn13G2l_e_exc) + # npn13G2L has fixed width (0.07um), Length could vary from 1:2.5 um + npn13G2l_e_pin = ( + npn13G2l_e_.with_bbox_min(um(0.07)) + .with_bbox_max(um(1), um(2.5)) + .with_area(um(0.07), um(0.175)) + ) + npn13G2l_b_pin = activ_mask.and_(npn_mk).not_(npn13G2l_b_exc) + npn13G2l_c_pin = npn13G2_c_pin + + npn13G2l_dev = ( + npn_dev.covering(npn13G2l_e_pin) + .covering(npn13G2l_b_pin) + .covering(npn13G2l_c_pin) + ) + npn13G2l_c = npn13G2l_dev.sized(nm(1)) + npn13G2l_tc = npn13G2l_c.not_(npn13G2l_dev).interacting(npn13G2l_c_pin) + npn13G2l_b = npn13G2l_dev.not_(npn13G2l_c_pin) + npn13G2l_tb = npn13G2l_b.not_(npn13G2l_e_pin).merged() + npn13G2l_e = npn13G2l_e_pin + npn13G2l_te = npn13G2l_e + + # # ---------- npn13G2V ---------- + # npn13G2V exclusion layers + npn13G2v_e_exc = activ_mask.join(nsd_block).join(emwind_drw).join(npn_exclude) + + # npn13G2V nodes + npn13G2v_e_ = emwihv_drw.and_(activ).and_(npn_mk).not_(npn13G2v_e_exc) + # npn13G2L has fixed width (0.12um), Length could vary from 1:5 um + npn13G2v_e_pin = ( + npn13G2v_e_.with_bbox_min(um(0.12)) + .with_bbox_max(um(1), um(5)) + .with_area(um(0.12), um(0.6)) + ) + npn13G2v_b_pin = npn13G2l_b_pin + npn13G2v_c_pin = npn13G2l_c_pin + + npn13G2v_dev = ( + npn_dev.covering(npn13G2v_e_pin) + .covering(npn13G2v_b_pin) + .covering(npn13G2v_c_pin) + ) + npn13G2v_c = npn13G2v_dev.sized(nm(1)) + npn13G2v_tc = npn13G2v_c.not_(npn13G2v_dev).interacting(npn13G2v_c_pin) + npn13G2v_b = npn13G2v_dev.not_(npn13G2v_c_pin) + npn13G2v_tb = npn13G2v_b.not_(npn13G2v_e_pin).merged() + npn13G2v_e = npn13G2v_e_pin + npn13G2v_te = npn13G2v_e + + # ============= + # ---- PNP ---- + # ============= + + deck.info("Starting PNP-BJT DERIVATIONS") + + pnp_exclude = ( + trans_drw.join(emwind_drw).join(emwihv_drw).join(nsd_block).join(bjt_exclude) + ) + + pnp_mk = ptap_holes.not_(pnp_exclude) + + # pnp general nodes DERIVATIONS + pnp_e = pactiv.and_(pnp_mk).and_(nwell_iso) + pnp_b = nactiv.and_(pnp_mk).and_(nwell_iso) + pnp_c = ptap.interacting(pnp_mk).not_(pnp_exclude) + + # pnp_mpa nodes DERIVATIONS + pnp_mpa_e = pnp_e.and_(pnp_b.extents()).and_(pnp_c.extents()) + pnp_mpa_b = pnp_b.interacting(pnp_b.extents().interacting(pnp_mpa_e)) + pnp_mpa_c = pnp_c.interacting(pnp_c.extents().interacting(pnp_mpa_e)) + + # diode_derivations.lvs + deck.info("Starting DIODE DERIVATIONS") + + diode_exclude = ( + gatpoly.join(nsd_drw) + .join(trans_drw) + .join(emwind_drw) + .join(emwihv_drw) + .join(polyres_drw) + .join(extblock_drw) + .join(res_drw) + .join(activ_mask) + .join(recog_esd) + .join(ind_drw) + .join(ind_pin) + .join(substrate_drw) + ) + + antenna_d_exc = pwell_block.join(salblock_drw).join(nsd_block).join(diode_exclude) + + antenna_d_mk = recog_diode.not_(antenna_d_exc) + + # ==== dantenna diode ==== + dantenna_n = activ.and_(antenna_d_mk).not_(psd_drw).not_(nwell_drw) + dantenna_p = pwell.and_(antenna_d_mk).covering(dantenna_n) + + # ==== dpantenna diode ==== + dpantenna_p = pactiv.and_(antenna_d_mk) + dpantenna_n = nwell_drw.covering(dpantenna_p) + + # ==== schottky_nbl1 diode ==== + schottky_mk = ( + recog_diode.and_(thickgateox_drw) + .not_(diode_exclude) + .and_(pwell_block) + .and_(ptap_holes) + .and_(nbulay_drw) + .and_(salblock_drw) + .and_(nsd_block) + .and_(nwell_holes) + .not_(psd_drw) + .not_(pwell) + .not_(diode_exclude) + ) + + schottcky_p_ = cont_drw.and_(activ).and_(metal1_con).and_(schottky_mk) + + # schottky_nbl1 is a fixed device (0.3um X 1.0 um) + schottcky_p = schottcky_p_.with_bbox_min(um(0.3)).with_bbox_max(um(1.0)) + # Using box with area 1x1 to be used as a reference to (m) + schottcky_p_1x1 = schottcky_p.middle(as_boxes).sized(um(0.499)) + + schottcky_n = nsd_block.and_(activ).covering(schottcky_p) + + # define port for schottcky + schottcky_n_port = ( + activ.interacting(nwell_iso) + .interacting(schottcky_n) + .not_(schottcky_n.sized(nm(-1))) + ) + schottcky_n_con = cont_drw.and_(schottcky_n_port).not_interacting(schottcky_p) + schottcky_sub = ptap.extents().covering(schottcky_p).covering(schottcky_n) + + # res_derivations.lvs + deck.info("Starting RESISTOR DERIVATIONS") + + polyres_exclude = ( + activ.join(pwell_block) + .join(nsd_block) + .join(nbulay_drw) + .join(thickgateox_drw) + .join(trans_drw) + .join(emwind_drw) + .join(emwihv_drw) + .join(activ_mask) + .join(recog_diode) + .join(recog_esd) + .join(ind_drw) + .join(ind_pin) + .join(substrate_drw) + ) + + # ============== + # ---- POLY ---- + # ============== + + ## polyres + polyres_mk = ( + polyres_drw.and_(extblock_drw).interacting(gatpoly).not_(polyres_exclude) + ) + + ## rhigh + rhigh_res = polyres_mk.and_(psd_drw).and_(nsd_drw).and_(salblock_drw) + rhigh_ports = gatpoly.interacting(rhigh_res).not_(rhigh_res) + + ## rppd + rppd_res = polyres_mk.and_(psd_drw).and_(salblock_drw).not_(nsd_block).not_(nsd_drw) + rppd_ports = gatpoly.interacting(rppd_res).not_(rppd_res) + + ## rsil + rsil_exc = psd_drw.join(salblock_drw).join(nsd_drw).join(nsd_block) + rsil_res = polyres_mk.and_(res_drw).not_(rsil_exc) + rsil_ports = gatpoly.interacting(rsil_res).not_(rsil_res) + + # =============== + # ---- METAL ---- + # =============== + + # res_metal1 + res_metal1 = metal1.and_(metal1_res) + + # res_metal2 + res_metal2 = metal2.and_(metal2_res) + + # res_metal3 + res_metal3 = metal3.and_(metal3_res) + + # res_metal4 + res_metal4 = metal4.and_(metal4_res) + + # res_metal5 + res_metal5 = metal5.and_(metal5_res) + + # res_topmetal1 + res_topmetal1 = topmetal1.and_(topmetal1_res) + + # res_topmetal2 + res_topmetal2 = topmetal2.and_(topmetal2_res) + + # cap_derivations.lvs + deck.info("Starting CAP DERIVATIONS") + + rfmimcap_exc = ind_drw.join(ind_pin) + + # === MIMCAP === + mimcap_exclude = pwell_block.join(rfmimcap_exc) + + mim_top = mim_drw.overlapping(topmetal1_con).and_(metal5_con) + mim_btm = metal5_con.and_(mim_drw).sized(um(0.6)) + mim_via = vmim_drw.join(topvia1_drw).and_(mim_drw) + topvia1_n_cap = topvia1_drw.not_(mim_via) + + # === cap_cmim === + cmim_top = mim_top.not_(mimcap_exclude) + cmim_btm = mim_btm.covering(cmim_top) + cmim_dev = mim_drw.covering(cmim_top).and_(cmim_btm) + + # === rfcmim === + rfmim_area = pwell_block.interacting(mim_drw) + rfmim_top = mim_top.and_(rfmim_area).not_(rfmimcap_exc) + rfmim_btm = mim_btm.and_(rfmim_area).covering(rfmim_top) + rfmim_dev = mim_drw.covering(rfmim_top).and_(rfmim_btm) + rfmim_sub = ptap.extents().interacting(rfmim_area) + rfmeas_mk = metal5_con.overlapping(rfmim_btm).and_(rfmim_area) + + # === svaricap === + cap_exc = ( + nsd_drw.join(trans_drw) + .join(emwind_drw) + .join(emwihv_drw) + .join(salblock_drw) + .join(polyres_drw) + .join(extblock_drw) + .join(res_drw) + .join(activ_mask) + .join(recog_diode) + .join(recog_esd) + .join(ind_drw) + .join(ind_pin) + .join(substrate_drw) + ) + + varicap_exc = pwell.join(pwell_block).join(nwell_holes).join(cap_exc) + + varicap_core = ngate_hv_base.and_(nwell_iso).not_(varicap_exc) + varicap_diff_port = ( + nactiv.interacting(varicap_core) + .not_(varicap_core) + .and_(nwell_iso) + .not_(varicap_exc) + .sized(nm(-1)) + ) + varicap_poly_port = gatpoly.interacting(varicap_core) + varicap_ports = varicap_poly_port.join(varicap_diff_port) + varicap_sub = ptap.and_(thickgateox_drw) + varicap_dev_mk = thickgateox_drw.covering(varicap_core).interacting(varicap_ports) + + # esd_derivations.lvs + deck.info("Starting ESD DERIVATIONS") + + # General + esd_exclude = ( + nsd_block.join(nsd_drw) + .join(trans_drw) + .join(emwind_drw) + .join(emwihv_drw) + .join(polyres_drw) + .join(extblock_drw) + .join(res_drw) + .join(substrate_drw) + .join(ind_drw) + .join(ind_pin) + ) + + esd_exc_d = gatpoly.join(thickgateox_drw).join(salblock_drw).join(esd_exclude) + + idiodevdd_exc = esd_exc_d.join(nwell_holes) + diodevdd_exc = idiodevdd_exc.join(nbulay_drw).join(pwell_block) + + idiodevss_exc = esd_exc_d.join(nwell_drw.not_interacting(nwell_holes)).join( + pwell_block + ) + diodevss_exc = idiodevss_exc.join(nbulay_drw) + + nw_diode = nwell_drw.not_interacting(pwell_block) + nw_idiode = nwell_iso.interacting(pwell_block) + + # ====================== + # ----- diode-ESD ----- + # ====================== + + # diodevdd_2kv + diodevdd_2kv_e = pactiv.and_(nw_diode).and_(recog_esd).not_(diodevdd_exc) + diodevdd_2kv_e_1x1 = diodevdd_2kv_e.middle().sized(um(0.499)) + diodevdd_2kv_b_ = nactiv.and_(nw_diode).and_(recog_esd).not_(diodevdd_exc) + diodevdd_2kv_b = diodevdd_2kv_b_.interacting( + diodevdd_2kv_b_.extents().interacting(diodevdd_2kv_e, 1, 1) + ) + diodevdd_2kv_tb = cont_drw.and_(diodevdd_2kv_b).not_interacting(diodevdd_2kv_e) + diodevdd_2kv_c_ = pactiv.and_(pwell).and_(recog_esd).not_(diodevdd_exc) + diodevdd_2kv_c = diodevdd_2kv_c_.interacting( + diodevdd_2kv_c_.extents().interacting(diodevdd_2kv_b, 1, 1) + ) + + # diodevdd_4kv + diodevdd_4kv_b = diodevdd_2kv_b_.interacting( + diodevdd_2kv_b_.extents().interacting(diodevdd_2kv_e, 2, 2) + ) + diodevdd_4kv_c = diodevdd_2kv_c_.interacting( + diodevdd_2kv_c_.extents().interacting(diodevdd_4kv_b, 1, 1) + ) + diodevdd_4kv_e = diodevdd_4kv_b.extents().sized(um(-0.15)) + diodevdd_4kv_e_1x1 = diodevdd_4kv_e.middle().sized(um(0.499)) + diodevdd_4kv_te = cont_drw.and_(diodevdd_2kv_e).not_interacting(diodevdd_4kv_b) + diodevdd_4kv_tb = cont_drw.and_(diodevdd_4kv_b).not_interacting(diodevdd_2kv_e) + + # diodevss_2kv + diodevss_2kv_e = nactiv.and_(pwell).and_(recog_esd).not_(diodevss_exc) + diodevss_2kv_e_1x1 = diodevss_2kv_e.middle().sized(um(0.499)) + diodevss_2kv_b_ = pactiv.and_(pwell).and_(recog_esd).not_(diodevss_exc) + diodevss_2kv_b = diodevss_2kv_b_.interacting( + diodevss_2kv_b_.extents().interacting(diodevss_2kv_e, 1, 1) + ) + diodevss_2kv_tb = cont_drw.and_(diodevss_2kv_b).not_interacting(diodevss_2kv_e) + diodevss_2kv_c_ = nactiv.and_(nw_diode).and_(recog_esd).not_(diodevss_exc) + diodevss_2kv_c = diodevss_2kv_c_.interacting( + diodevss_2kv_c_.extents().interacting(diodevss_2kv_b, 1, 1) + ) + + # diodevss_4kv + diodevss_4kv_b = diodevss_2kv_b_.interacting( + diodevss_2kv_b_.extents().interacting(diodevss_2kv_e, 2, 2) + ) + diodevss_4kv_c = diodevss_2kv_c_.interacting( + diodevss_2kv_c_.extents().interacting(diodevss_4kv_b, 1, 1) + ) + diodevss_4kv_e = diodevss_4kv_b.extents().sized(um(-0.15)) + diodevss_4kv_e_1x1 = diodevss_4kv_e.middle().sized(um(0.499)) + diodevss_4kv_te = cont_drw.and_(diodevss_2kv_e).not_interacting(diodevss_4kv_b) + diodevss_4kv_tb = cont_drw.and_(diodevss_4kv_b).not_interacting(diodevss_2kv_e) + + # ======================= + # ----- idiode-ESD ----- + # ======================= + + # idiodevdd_2kv + idiodevdd_2kv_e = pactiv.and_(nw_idiode).and_(recog_esd).not_(idiodevdd_exc) + idiodevdd_2kv_e_1x1 = idiodevdd_2kv_e.middle().sized(um(0.499)) + idiodevdd_2kv_b_ = nactiv.and_(nw_idiode).and_(recog_esd).not_(idiodevdd_exc) + idiodevdd_2kv_b = idiodevdd_2kv_b_.interacting( + idiodevdd_2kv_b_.extents().interacting(idiodevdd_2kv_e, 1, 1) + ) + idiodevdd_2kv_tb = cont_drw.and_(idiodevdd_2kv_b).not_interacting(idiodevdd_2kv_e) + idiodevdd_2kv_c_ = pactiv.and_(pwell).and_(recog_esd).not_(idiodevdd_exc) + idiodevdd_2kv_c = idiodevdd_2kv_c_.interacting( + idiodevdd_2kv_c_.extents().interacting(idiodevdd_2kv_b, 1, 1) + ) + + # idiodevdd_4kv + idiodevdd_4kv_b = idiodevdd_2kv_b_.interacting( + idiodevdd_2kv_b_.extents().interacting(idiodevdd_2kv_e, 2, 2) + ) + idiodevdd_4kv_c = idiodevdd_2kv_c_.interacting( + idiodevdd_2kv_c_.extents().interacting(idiodevdd_4kv_b, 1, 1) + ) + idiodevdd_4kv_e = idiodevdd_4kv_b.extents().sized(um(-0.15)) + idiodevdd_4kv_e_1x1 = idiodevdd_4kv_e.middle().sized(um(0.499)) + idiodevdd_4kv_te = cont_drw.and_(idiodevdd_2kv_e).not_interacting(idiodevdd_4kv_b) + idiodevdd_4kv_tb = cont_drw.and_(idiodevdd_4kv_b).not_interacting(idiodevdd_2kv_e) + + # idiodevss_2kv + idiodevss_2kv_e = ( + nactiv.and_(pwell).and_(nbulay_drw).and_(recog_esd).not_(idiodevss_exc) + ) + idiodevss_2kv_e_1x1 = idiodevss_2kv_e.middle().sized(um(0.499)) + idiodevss_2kv_b_ = ( + pactiv.and_(pwell).and_(nbulay_drw).and_(recog_esd).not_(idiodevss_exc) + ) + idiodevss_2kv_b = idiodevss_2kv_b_.interacting( + idiodevss_2kv_b_.extents().interacting(idiodevss_2kv_e, 1, 1) + ) + idiodevss_2kv_tb = cont_drw.and_(idiodevss_2kv_b).not_interacting(idiodevss_2kv_e) + idiodevss_2kv_c_ = nactiv.and_(nwell_iso).and_(recog_esd).not_(idiodevss_exc) + idiodevss_2kv_c = idiodevss_2kv_c_.interacting( + idiodevss_2kv_c_.extents().interacting(idiodevss_2kv_b, 1, 1) + ) + + # idiodevss_4kv + idiodevss_4kv_b = idiodevss_2kv_b_.interacting( + idiodevss_2kv_b_.extents().interacting(idiodevss_2kv_e, 2, 2) + ) + idiodevss_4kv_c = idiodevss_2kv_c_.interacting( + idiodevss_2kv_c_.extents().interacting(idiodevss_4kv_b, 1, 1) + ) + idiodevss_4kv_e = idiodevss_4kv_b.extents().sized(um(-0.15)) + idiodevss_4kv_e_1x1 = idiodevss_4kv_e.middle().sized(um(0.499)) + idiodevss_4kv_te = cont_drw.and_(idiodevss_2kv_e).not_interacting(idiodevss_4kv_b) + idiodevss_4kv_tb = cont_drw.and_(idiodevss_4kv_b).not_interacting(idiodevss_2kv_e) + + # ====================== + # ----- MOSCL-ESD ----- + # ====================== + + nmoscl_exc = esd_exclude.join(pwell_block) + + # nmoscl_2 + nmoscl_2_patt = glob_to_case_insensitive_glob("nmoscl_2") + + gate_moscl = ngate_hv_base.and_(salblock_drw).and_(nbulay_drw) + nmoscl_2_n_ = recog_esd.interacting(text_drw.texts(nmoscl_2_patt)) + nmoscl_2_n = nmoscl_2_n_.interacting(gate_moscl, 12) + nmoscl_2_n_port = nwell_drw.and_(nmoscl_2_n) + nmoscl_2_p_ = ptap.and_(nbulay_drw).inside(nmoscl_2_n_) + # Using box with area 1x1 to be used as a reference to (m) + nmoscl_2_p = nmoscl_2_p_.middle(as_boxes).sized(um(0.499)) + + # nmoscl_4 + nmoscl_4_patt = glob_to_case_insensitive_glob("nmoscl_4") + + gate_moscl = ngate_hv_base.and_(salblock_drw).and_(nbulay_drw) + nmoscl_4_n_ = recog_esd.interacting(text_drw.texts(nmoscl_4_patt)) + nmoscl_4_n = nmoscl_4_n_.interacting(gate_moscl, 24) + nmoscl_4_n_port = nwell_drw.and_(nmoscl_4_n) + nmoscl_4_p_ = ptap.and_(nbulay_drw).inside(nmoscl_4_n_) + # Using box with area 1x1 to be used as a reference to (m) + nmoscl_4_p = nmoscl_4_p_.middle(as_boxes).sized(um(0.499)) + + # ind_derivations.lvs + deck.info("Starting Inductor DERIVATIONS") + + ind_exc = ( + gatpoly.join(nsd_drw) + .join(nbulay_drw) + .join(thickgateox_drw) + .join(emwind_drw) + .join(emwihv_drw) + .join(salblock_drw) + .join(polyres_drw) + .join(mim_drw) + .join(extblock_drw) + .join(res_drw) + .join(activ_mask) + .join(recog_diode) + .join(recog_esd) + .join(substrate_drw) + ) + + # General + la_patt = glob_to_case_insensitive_glob("LA") + lb_patt = glob_to_case_insensitive_glob("LB") + lc_patt = glob_to_case_insensitive_glob("LC") + ind2_patt = glob_to_case_insensitive_glob("inductor2*") + ind3_patt = glob_to_case_insensitive_glob("inductor3*") + + ind_edges = ind_drw.edges() + ind_core_ = topmetal2.join(topmetal1).and_(ind_drw).merged().not_(ind_exc) + ind_ports_ = ind_pin.and_(ind_core_).interacting(ind_edges) + ind_port_la = ind_ports_.interacting(ind_text.texts(la_patt)) + ind_la_tm1 = ind_port_la.and_(topmetal1) + ind_port_lb = ind_ports_.interacting(ind_text.texts(lb_patt)) + ind_lb_tm1 = ind_port_lb.and_(topmetal1) + ind_port_lc = ind_ports_.interacting(ind_text.texts(lc_patt)) + ind_lc_tm2 = ind_port_lc.and_(topmetal2) + + # inductor2 + ind2_ports = ind_port_la.join(ind_port_lb) + ind2_core = ind_core_.interacting(ind_port_la, 1).interacting(ind_port_lb, 1) + ind2_mk_ = ind_drw.interacting(text_drw.texts(ind2_patt)) + ind2_mk = ( + ind2_mk_.interacting(ind2_core) + .interacting(ind2_ports) + .not_interacting(ind_port_lc) + ) + ind2_sub1 = pwell.and_(ind_drw).interacting(ind2_core) + ind2_sub2 = pwell_block.and_(ind_drw).interacting(ind2_core).sized(nm(1)) + ind2_well = nwell_drw.and_(ind_drw).interacting(ind2_core).sized(nm(-1)) + ind2_sub = ind2_sub1.join(ind2_sub2).join(ind2_well) + # inductor3 + ind3_ports = ind_la_tm1.join(ind_lb_tm1).join(ind_lc_tm2) + ind3_core = ( + ind_core_.interacting(ind_lb_tm1, 1) + .interacting(ind_lb_tm1, 1) + .interacting(ind_lc_tm2, 1) + ) + ind3_mk_ = ind_drw.interacting(text_drw.texts(ind3_patt)) + ind3_mk = ind3_mk_.interacting(ind3_core).interacting(ind3_ports) + ind3_sub1 = pwell.and_(ind_drw).interacting(ind3_core) + ind3_sub2 = pwell_block.and_(ind_drw).interacting(ind3_core).sized(nm(1)) + ind3_well = nwell_drw.and_(ind_drw).interacting(ind3_core).sized(nm(-1)) + ind3_sub = ind3_sub1.join(ind3_sub2).join(ind3_well) + + # tap_derivations.lvs + deck.info("Starting Taps DERIVATIONS") + + taps_exclude = ( + gatpoly.join(nsd_drw) + .join(trans_drw) + .join(emwind_drw) + .join(emwihv_drw) + .join(salblock_drw) + .join(polyres_drw) + .join(extblock_drw) + .join(res_drw) + .join(activ_mask) + .join(recog_diode) + .join(recog_esd) + .join(ind_drw) + .join(ind_pin) + ) + + # === ntap1 === + ntap1_exc = pwell.join(psd_drw).join(taps_exclude) + + ntap1_tie = nactiv.and_(ntap1_mk).extents().not_(ntap1_exc) + ntap1_well = ntap1_mk.covering(ntap1_tie) + + # === ptap1 === + ptap1_exc = nwell_drw.join(taps_exclude) + + ptap1_tie = pactiv.and_(ptap1_mk).extents().not_(ptap1_exc) + ptap1_sub = pwell.covering(ptap1_tie) + + # devices_connections.lvs + deck.info("Starting GF180 LVS connectivity setup") + + # ================================ + # ----- GENERAL CONNECTIONS ----- + # ================================ + + # ================================ + # ----- MOSFET CONNECTIONS ------ + # ================================ + + # ================================ + # ---- RF-MOSFET CONNECTIONS ---- + # ================================ + + # ================================ + # ------ BJT CONNECTIONS -------- + # ================================ + + # ================================ + # ----- DIODE CONNECTIONS ------- + # ================================ + + # ================================ + # ------- RES CONNECTIONS ------- + # ================================ + + # ================================== + # -------- CAP CONNECTIONS -------- + # ================================== + + # ================================ + # ------- ESD CONNECTIONS ------- + # ================================ + + # ================================= + # ----- Inductor CONNECTIONS ----- + # ================================= + + # ================================ + # ------- Taps CONNECTIONS ------ + # ================================ + + # general_connections.lvs + deck.info("Starting GF180 LVS connectivity setup (Inter-layer)") + + # Inter-layer + deck.connect(pwell_sub, pwell) + deck.connect(pwell, ptap) + deck.connect(nwell_drw, ntap) + deck.connect(ntap, cont_drw) + deck.connect(ptap, cont_drw) + deck.connect(poly_con, cont_drw) + deck.connect(nsd_fet, cont_drw) + deck.connect(psd_fet, cont_drw) + deck.connect(cont_drw, metal1_con) + deck.connect(metal1_con, via1_drw) + deck.connect(via1_drw, metal2_con) + deck.connect(metal2_con, via2_drw) + deck.connect(via2_drw, metal3_con) + deck.connect(metal3_con, via3_drw) + deck.connect(via3_drw, metal4_con) + deck.connect(metal4_con, via4_drw) + deck.connect(via4_drw, metal5_con) + deck.connect(metal5_con, topvia1_n_cap) + deck.connect(topvia1_n_cap, topmetal1_con) + deck.connect(topmetal1_con, topvia2_drw) + deck.connect(topvia2_drw, topmetal2_con) + + # salicide connection + deck.connect(nsd_fet, nsd_ptap_abutt) + deck.connect(nsd_ptap_abutt, ptap) + deck.connect(psd_fet, psd_ntap_abutt) + deck.connect(psd_ntap_abutt, ntap) + + # Attaching labels + deck.connect(metal1_con, metal1_text) + deck.connect(metal2_con, metal2_text) + deck.connect(metal3_con, metal3_text) + deck.connect(metal4_con, metal4_text) + deck.connect(metal5_con, metal5_text) + deck.connect(topmetal1_con, topmetal1_text) + deck.connect(topmetal2_con, topmetal2_text) + + deck.info("Starting SG13G2 LVS connectivity setup (Global)") + + # mos_connections.lvs + deck.info("Starting LVS MOSFET CONNECTIONS") + + # rfmos_connections.lvs + deck.info("Starting LVS RF-MOSFET CONNECTIONS") + + # bjt_connections.lvs + deck.info("Starting LVS BJT CONNECTIONS") + + # ============= + # ---- NPN ---- + # ============= + + # General + deck.connect(npn_sub, pwell) + + # npn13G2 nodes connections + deck.connect(npn13G2_te, npn13G2_e_pin) + deck.connect(npn13G2_tc, npn13G2_c_pin) + deck.connect(npn13G2_tb, npn13G2_b_pin) + deck.connect(npn13G2_e_pin, cont_drw) + deck.connect(npn13G2_c_pin, cont_drw) + deck.connect(npn13G2_b_pin, cont_drw) + deck.connect(npn_sub, pwell) + deck.connect(npn13G2_e_pin, emwind_drw) + deck.connect(emwind_drw, metal1_con) + + # npn13G2L nodes connections + deck.connect(npn13G2l_te, npn13G2l_e_pin) + deck.connect(npn13G2l_tc, npn13G2l_c_pin) + deck.connect(npn13G2l_tb, npn13G2l_b_pin) + deck.connect(npn13G2l_e_pin, cont_drw) + deck.connect(npn13G2l_c_pin, cont_drw) + deck.connect(npn13G2l_b_pin, cont_drw) + deck.connect(npn_sub, pwell) + + # npn13G2V nodes connections + deck.connect(npn13G2v_te, npn13G2v_e_pin) + deck.connect(npn13G2v_tc, npn13G2v_c_pin) + deck.connect(npn13G2v_tb, npn13G2v_b_pin) + deck.connect(npn13G2v_e_pin, cont_drw) + deck.connect(npn13G2v_c_pin, cont_drw) + deck.connect(npn13G2v_b_pin, cont_drw) + deck.connect(npn_sub, pwell) + + # ============= + # ---- PNP ---- + # ============= + + # pnp_mpa nodes connections + deck.connect(pnp_mpa_e, cont_drw) + deck.connect(pnp_mpa_b, cont_drw) + deck.connect(pnp_mpa_c, cont_drw) + + # diode_connections.lvs + deck.info("Starting LVS DIODE CONNECTIONS") + + # dantenna diode + deck.connect(dantenna_n, cont_drw) + deck.connect(dantenna_p, pwell) + + # dantenna diode + deck.connect(dpantenna_n, nwell_drw) + deck.connect(dpantenna_p, cont_drw) + + # dantenna diode + deck.connect(schottcky_p_1x1, schottcky_p) + deck.connect(schottcky_p, metal1_con) + deck.connect(schottcky_n, schottcky_n_port) + deck.connect(schottcky_n_port, schottcky_n_con) + deck.connect(schottcky_n_con, metal1_con) + + # res_connections.lvs + deck.info("Starting RESISTOR CONNECTIONS") + + # === rsil === + deck.connect(rsil_ports, cont_drw) + + # === rppd === + deck.connect(rppd_ports, cont_drw) + + # === rhigh === + deck.connect(rhigh_ports, cont_drw) + + # === Metal Res === + # Added in general connections + + # cap_connections.lvs + deck.info("Starting LVS CAP CONNECTIONS") + + # === cap_mim === + deck.connect(cmim_btm, metal5_con) + deck.connect(cmim_top, mim_via) + deck.connect(mim_via, topmetal1_con) + + # === rfcmim === + deck.connect(rfmim_btm, metal5_con) + deck.connect(rfmim_top, mim_via) + deck.connect(rfmim_sub, ptap) + + # === svarivap === + deck.connect(varicap_ports, cont_drw) + deck.connect(varicap_ports, text_drw) + deck.connect(varicap_sub, ptap) + + # esd_connections.lvs + deck.info("Starting ESD CONNECTIONS") + + # ====================== + # ----- diode-ESD ----- + # ====================== + + # diodevdd_2kv + deck.connect(diodevdd_2kv_e_1x1, diodevdd_2kv_e) + deck.connect(diodevdd_2kv_e, cont_drw) + deck.connect(diodevdd_2kv_b, diodevdd_2kv_tb) + deck.connect(diodevdd_2kv_tb, cont_drw) + deck.connect(diodevdd_2kv_c, cont_drw) + + # diodevdd_4kv + deck.connect(diodevdd_4kv_e_1x1, diodevdd_4kv_e) + deck.connect(diodevdd_4kv_e, diodevdd_4kv_te) + deck.connect(diodevdd_4kv_te, cont_drw) + deck.connect(diodevdd_4kv_b, diodevdd_4kv_tb) + deck.connect(diodevdd_4kv_tb, cont_drw) + deck.connect(diodevdd_4kv_c, cont_drw) + + # diodevss_2kv + deck.connect(diodevss_2kv_e_1x1, diodevss_2kv_e) + deck.connect(diodevss_2kv_e, cont_drw) + deck.connect(diodevss_2kv_b, diodevss_2kv_tb) + deck.connect(diodevss_2kv_tb, cont_drw) + deck.connect(diodevss_2kv_c, cont_drw) + + # diodevss_4kv + deck.connect(diodevss_4kv_e_1x1, diodevss_4kv_e) + deck.connect(diodevss_4kv_e, diodevss_4kv_te) + deck.connect(diodevss_4kv_te, cont_drw) + deck.connect(diodevss_4kv_b, diodevss_4kv_tb) + deck.connect(diodevss_4kv_tb, cont_drw) + deck.connect(diodevss_4kv_c, cont_drw) + + # ======================= + # ----- idiode-ESD ----- + # ======================= + + # idiodevdd_2kv + deck.connect(idiodevdd_2kv_e_1x1, idiodevdd_2kv_e) + deck.connect(idiodevdd_2kv_e, cont_drw) + deck.connect(idiodevdd_2kv_b, idiodevdd_2kv_tb) + deck.connect(idiodevdd_2kv_tb, cont_drw) + deck.connect(idiodevdd_2kv_c, cont_drw) + + # idiodevdd_4kv + deck.connect(idiodevdd_4kv_e_1x1, idiodevdd_4kv_e) + deck.connect(idiodevdd_4kv_e, idiodevdd_4kv_te) + deck.connect(idiodevdd_4kv_te, cont_drw) + deck.connect(idiodevdd_4kv_b, idiodevdd_4kv_tb) + deck.connect(idiodevdd_4kv_tb, cont_drw) + deck.connect(idiodevdd_4kv_c, cont_drw) + + # idiodevss_2kv + deck.connect(idiodevss_2kv_e_1x1, idiodevss_2kv_e) + deck.connect(idiodevss_2kv_e, cont_drw) + deck.connect(idiodevss_2kv_b, idiodevss_2kv_tb) + deck.connect(idiodevss_2kv_tb, cont_drw) + deck.connect(idiodevss_2kv_c, cont_drw) + + # idiodevss_4kv + deck.connect(idiodevss_4kv_e_1x1, idiodevss_4kv_e) + deck.connect(idiodevss_4kv_e, idiodevss_4kv_te) + deck.connect(idiodevss_4kv_te, cont_drw) + deck.connect(idiodevss_4kv_b, idiodevss_4kv_tb) + deck.connect(idiodevss_4kv_tb, cont_drw) + deck.connect(idiodevss_4kv_c, cont_drw) + + # ====================== + # ----- MOSCL-ESD ----- + # ====================== + + # nmoscl_2 + deck.connect(nmoscl_2_n, nmoscl_2_n_port) + deck.connect(nmoscl_2_n_port, cont_drw) + deck.connect(nmoscl_2_p, nmoscl_2_p_) + deck.connect(nmoscl_2_p_, ptap) + + # nmoscl_4 + deck.connect(nmoscl_4_n, nmoscl_4_n_port) + deck.connect(nmoscl_4_n_port, cont_drw) + deck.connect(nmoscl_4_p, nmoscl_4_p_) + deck.connect(nmoscl_4_p_, ptap) + + # ind_connections.lvs + deck.info("Starting Inductor CONNECTIONS") + + # ind2 + deck.connect(ind2_ports, ind_pin) + deck.connect(ind_pin, ind_text) + deck.connect(ind_pin, topmetal2_con) + deck.connect(ind2_sub, pwell) + deck.connect(ind2_sub, nwell_drw) + + # ind3 + deck.connect(ind3_ports, ind_pin) + deck.connect(ind_pin, topmetal1_con) + deck.connect(ind3_sub, pwell) + deck.connect(ind3_sub, nwell_drw) + + # tap_connections.lvs + deck.info("Starting LVS Taps CONNECTIONS") + + # ntap1 + deck.connect(ntap1_tie, cont_drw) + deck.connect(ntap1_well, nwell_drw) + + # ptap1 + deck.connect(ptap1_tie, cont_drw) + deck.connect(ptap1_sub, pwell) + + # mos_extraction.lvs + deck.info("Starting MOSFET EXTRACTION") + + # ============== + # ---- NMOS ---- + # ============== + + deck.info("Starting NMOS EXTRACTION") + + # nmos - LV + deck.info("Extraction of NMOS-LV transistor") + deck.extract_devices( + klayout_lvs.mos4("sg13_lv_nmos"), + { + "SD": nsd_fet, + "G": ngate_lv, + "tS": nsd_fet, + "tD": nsd_fet, + "tG": poly_con, + "W": pwell, + }, + ) + + # nmos - HV + deck.info("Extraction of NMOS-HV transistor") + deck.extract_devices( + klayout_lvs.mos4("sg13_hv_nmos"), + { + "SD": nsd_fet, + "G": ngate_hv, + "tS": nsd_fet, + "tD": nsd_fet, + "tG": poly_con, + "W": pwell, + }, + ) + + # ============== + # ---- PMOS ---- + # ============== + + deck.info("Starting PMOS EXTRACTION") + + # pmos - LV + deck.info("Extraction of PMOS-LV transistor") + deck.extract_devices( + klayout_lvs.mos4("sg13_lv_pmos"), + { + "SD": psd_fet, + "G": pgate_lv, + "tS": psd_fet, + "tD": psd_fet, + "tG": poly_con, + "W": nwell_drw, + }, + ) + + # pmos - HV + deck.info("Extraction of PMOS-HV transistor") + deck.extract_devices( + klayout_lvs.mos4("sg13_hv_pmos"), + { + "SD": psd_fet, + "G": pgate_hv, + "tS": psd_fet, + "tD": psd_fet, + "tG": poly_con, + "W": nwell_drw, + }, + ) + + # rfmos_extraction.lvs + deck.info("Starting RF-MOSFET EXTRACTION") + + # =============== + # --- RF-NMOS --- + # =============== + + # rfnmos - LV + deck.info("Extraction of RF-NMOS-LV transistor") + deck.extract_devices( + klayout_lvs.mos4("rfnmos"), + { + "SD": nsd_fet, + "G": rfngate_lv, + "W": pwell, + "tS": nsd_fet, + "tD": nsd_fet, + "tG": poly_con, + "tW": ptap, + }, + ) + + # rfnmos - HV + deck.info("Extraction of RF-NMOS-HV transistor") + deck.extract_devices( + klayout_lvs.mos4("rfnmoshv"), + { + "SD": nsd_fet, + "G": rfngate_hv, + "W": pwell, + "tS": nsd_fet, + "tD": nsd_fet, + "tG": poly_con, + "tW": ptap, + }, + ) + + # =============== + # --- RF-PMOS --- + # =============== + + # rfpmos - LV + deck.info("Extraction of RF-PMOS-LV transistor") + deck.extract_devices( + klayout_lvs.mos4("rfpmos"), + { + "SD": psd_fet, + "G": rfpgate_lv, + "tS": psd_fet, + "tD": psd_fet, + "tG": poly_con, + "W": nwell_drw, + "tW": ntap, + }, + ) + + # rfpmos - HV + deck.info("Extraction of RF-PMOS-HV transistor") + deck.extract_devices( + klayout_lvs.mos4("rfpmoshv"), + { + "SD": psd_fet, + "G": rfpgate_hv, + "tS": psd_fet, + "tD": psd_fet, + "tG": poly_con, + "W": nwell_drw, + "tW": ntap, + }, + ) + + # bjt_extraction.lvs + deck.info("Starting BJT EXTRACTION") + + # ============= + # ---- NPN ---- + # ============= + + deck.info("Starting NPN-BJT EXTRACTION") + + deck.info("Extraction of npn13G2 BJT transistor") + deck.extract_devices( + klayout_lvs.bjt4("npn13G2", CustomBJT4), + { + "C": npn13G2_c, + "B": npn13G2_b, + "E": npn13G2_e, + "S": npn_sub, + "tC": npn13G2_tc, + "tB": npn13G2_tb, + "tE": npn13G2_te, + "tS": npn_sub, + }, + ) + + deck.info("Extraction of npn13G2L BJT transistor") + deck.extract_devices( + klayout_lvs.bjt4("npn13G2l", CustomBJT4), + { + "C": npn13G2l_c, + "B": npn13G2l_b, + "E": npn13G2l_e, + "S": npn_sub, + "tC": npn13G2l_tc, + "tB": npn13G2l_tb, + "tE": npn13G2l_te, + "tS": npn_sub, + }, + ) + + deck.info("Extraction of npn13G2V BJT transistor") + deck.extract_devices( + klayout_lvs.bjt4("npn13G2v", CustomBJT4), + { + "C": npn13G2v_c, + "B": npn13G2v_b, + "E": npn13G2v_e, + "S": npn_sub, + "tC": npn13G2v_tc, + "tB": npn13G2v_tb, + "tE": npn13G2v_te, + "tS": npn_sub, + }, + ) + + # ============= + # ---- PNP ---- + # ============= + + deck.info("Starting PNP-BJT EXTRACTION") + + # pnp_mpa BJT + deck.info("Extracting pnpMPA BJT") + deck.extract_devices( + klayout_lvs.bjt3("pnpMPA", CustomBJT3), + { + "C": pnp_mpa_c.extents(), + "B": pnp_mpa_b.extents(), + "E": pnp_mpa_e, + "tC": pnp_mpa_c, + "tB": pnp_mpa_b, + "tE": pnp_mpa_e, + }, + ) + + # diode_extraction.lvs + deck.info("Starting DIODE EXTRACTION") + + # dantenna diode + deck.info("Extracting dantenna diode") + deck.extract_devices( + klayout_lvs.diode("dantenna", EnDiode), {"N": dantenna_n, "P": dantenna_p} + ) + + # dpantenna diode + deck.info("Extracting dpantenna diode") + deck.extract_devices( + klayout_lvs.diode("dpantenna", EnDiode), {"N": dpantenna_n, "P": dpantenna_p} + ) + + # schottky_nbl1 diode + deck.info("Extracting schottky_nbl1 diode") + deck.extract_devices( + klayout_lvs.bjt3("schottky_nbl1", Esd3Term), + { + "C": schottcky_sub, + "B": schottcky_n_port.extents(), + "E": schottcky_p_1x1, + "tC": ptap, + "tB": schottcky_n_port, + "tE": schottcky_p_1x1, + }, + ) + + # res_extraction.lvs + deck.info("Starting RESISTOR EXTRACTION") + + # ============== + # ---- POLY ---- + # ============== + + # rsil + deck.info("Extracting rsil resistor") + deck.extract_devices( + GeneralNTerminalExtractor("rsil", 2), + { + "core": rsil_res, + "ports": rsil_ports, + "meas_mk": polyres_drw, + "dev_mk": polyres_drw.interacting(rsil_res), + "sub_mk": pwell.join(nwell_drw), + }, + ) + # rppd + deck.info("Extracting rppd resistor") + deck.extract_devices( + GeneralNTerminalExtractor("rppd", 2), + { + "core": rppd_res, + "ports": rppd_ports, + "meas_mk": polyres_drw, + "dev_mk": polyres_drw.interacting(rppd_res), + "sub_mk": pwell.join(nwell_drw), + }, + ) + + # rhigh + deck.info("Extracting rhigh resistor") + deck.extract_devices( + GeneralNTerminalExtractor("rhigh", 2), + { + "core": rhigh_res, + "ports": rhigh_ports, + "meas_mk": polyres_drw, + "dev_mk": polyres_drw.interacting(rhigh_res), + "sub_mk": pwell.join(nwell_drw), + }, + ) + + # =============== + # ---- METAL ---- + # =============== + + # RM1 + deck.info("Extracting res_metal1 resistor") + deck.extract_devices( + klayout_lvs.resistor("res_metal1", 1.0, RES2), + {"R": res_metal1, "C": metal1_con}, + ) + + # RM2 + deck.info("Extracting res_metal2 resistor") + deck.extract_devices( + klayout_lvs.resistor("res_metal2", 1.0, RES2), + {"R": res_metal2, "C": metal2_con}, + ) + + # RM3 + deck.info("Extracting res_metal3 resistor") + deck.extract_devices( + klayout_lvs.resistor("res_metal3", 1.0, RES2), + {"R": res_metal3, "C": metal3_con}, + ) + + # RM4 + deck.info("Extracting res_metal4 resistor") + deck.extract_devices( + klayout_lvs.resistor("res_metal4", 1.0, RES2), + {"R": res_metal4, "C": metal4_con}, + ) + + # RM5 + deck.info("Extracting res_metal5 resistor") + deck.extract_devices( + klayout_lvs.resistor("res_metal5", 1.0, RES2), + {"R": res_metal5, "C": metal5_con}, + ) + + # RTM1 + deck.info("Extracting res_topmetal1 resistor") + deck.extract_devices( + klayout_lvs.resistor("res_topmetal1", 1.0, RES2), + {"R": res_topmetal1, "C": topmetal1_con}, + ) + + # RTM2 + deck.info("Extracting res_topmetal2 resistor") + deck.extract_devices( + klayout_lvs.resistor("res_topmetal2", 1.0, RES2), + {"R": res_topmetal2, "C": topmetal2_con}, + ) + + # cap_extraction.lvs + deck.info("Starting CAP EXTRACTION") + + # === cap_cmim === + deck.info("Extracting cap_cmim device") + deck.extract_devices( + MIMCAPExtractor("cap_cmim"), + { + "core": cmim_top, + "top_mim": cmim_top, + "btm_mim": cmim_btm, + "meas_mk": cmim_dev, + "dev_mk": cmim_dev, + }, + ) + + # === rfcmim === + deck.info("Extracting rfcmim device") + deck.extract_devices( + MIMCAPExtractor("rfcmim"), + { + "core": rfmim_top, + "top_mim": rfmim_top, + "btm_mim": rfmim_btm, + "dev_mk": rfmim_area.covering(rfmim_dev), + "meas_mk": rfmeas_mk, + "sub_mk": rfmim_sub, + }, + ) + + # === svaricap === + deck.info("Extracting sg13_hv_svaricap varactor") + deck.extract_devices( + GeneralNTerminalExtractor("sg13_hv_svaricap", 3), + { + "core": varicap_core, + "ports": varicap_ports, + "meas_mk": activ, + "dev_mk": varicap_dev_mk, + "sub_mk": varicap_sub, + }, + ) + + # esd_extraction.lvs + deck.info("Starting ESD EXTRACTION") + + # ====================== + # ----- diode-ESD ----- + # ====================== + + # diodevdd_2kv + deck.info("Extracting diodevdd_2kv ESD device") + deck.extract_devices( + klayout_lvs.bjt3("diodevdd_2kv", Esd3Term), + { + "C": diodevdd_2kv_c.extents(), + "B": diodevdd_2kv_b.extents(), + "E": diodevdd_2kv_e_1x1, + "tC": diodevdd_2kv_c, + "tB": diodevdd_2kv_b, + "tE": diodevdd_2kv_e_1x1, + }, + ) + + # diodevdd_4kv + deck.info("Extracting diodevdd_4kv ESD device") + deck.extract_devices( + klayout_lvs.bjt3("diodevdd_4kv", Esd3Term), + { + "C": diodevdd_4kv_c.extents(), + "B": diodevdd_4kv_b.extents(), + "E": diodevdd_4kv_e_1x1, + "tC": diodevdd_4kv_c, + "tB": diodevdd_4kv_b, + "tE": diodevdd_4kv_e_1x1, + }, + ) + + # diodevss_2kv + deck.info("Extracting diodevss_2kv ESD device") + deck.extract_devices( + klayout_lvs.bjt3("diodevss_2kv", Esd3Term), + { + "C": diodevss_2kv_c.extents(), + "B": diodevss_2kv_b.extents(), + "E": diodevss_2kv_e_1x1, + "tC": diodevss_2kv_c, + "tB": diodevss_2kv_b, + "tE": diodevss_2kv_e_1x1, + }, + ) + + # diodevss_4kv + deck.info("Extracting diodevss_4kv ESD device") + deck.extract_devices( + klayout_lvs.bjt3("diodevss_4kv", Esd3Term), + { + "C": diodevss_4kv_c.extents(), + "B": diodevss_4kv_b.extents(), + "E": diodevss_4kv_e_1x1, + "tC": diodevss_4kv_c, + "tB": diodevss_4kv_b, + "tE": diodevss_4kv_e_1x1, + }, + ) + + # ======================= + # ----- idiode-ESD ----- + # ======================= + + # idiodevdd_2kv + deck.info("Extracting idiodevdd_2kv ESD device") + deck.extract_devices( + klayout_lvs.bjt3("idiodevdd_2kv", Esd3Term), + { + "C": idiodevdd_2kv_c.extents(), + "B": idiodevdd_2kv_b.extents(), + "E": idiodevdd_2kv_e_1x1, + "tC": idiodevdd_2kv_c, + "tB": idiodevdd_2kv_b, + "tE": idiodevdd_2kv_e_1x1, + }, + ) + + # idiodevdd_4kv + deck.info("Extracting idiodevdd_4kv ESD device") + deck.extract_devices( + klayout_lvs.bjt3("idiodevdd_4kv", Esd3Term), + { + "C": idiodevdd_4kv_c.extents(), + "B": idiodevdd_4kv_b.extents(), + "E": idiodevdd_4kv_e_1x1, + "tC": idiodevdd_4kv_c, + "tB": idiodevdd_4kv_b, + "tE": idiodevdd_4kv_e_1x1, + }, + ) + + # idiodevss_2kv + deck.info("Extracting idiodevss_2kv ESD device") + deck.extract_devices( + klayout_lvs.bjt3("idiodevss_2kv", Esd3Term), + { + "C": idiodevss_2kv_c.extents(), + "B": idiodevss_2kv_b.extents(), + "E": idiodevss_2kv_e_1x1, + "tC": idiodevss_2kv_c, + "tB": idiodevss_2kv_b, + "tE": idiodevss_2kv_e_1x1, + }, + ) + + # idiodevss_4kv + deck.info("Extracting idiodevss_4kv ESD device") + deck.extract_devices( + klayout_lvs.bjt3("idiodevss_4kv", Esd3Term), + { + "C": idiodevss_4kv_c.extents(), + "B": idiodevss_4kv_b.extents(), + "E": idiodevss_4kv_e_1x1, + "tC": idiodevss_4kv_c, + "tB": idiodevss_4kv_b, + "tE": idiodevss_4kv_e_1x1, + }, + ) + + # ====================== + # ----- MOSCL-ESD ----- + # ====================== + + # nmoscl_2 + deck.info("Extracting nmoscl_2 ESD device") + deck.extract_devices( + klayout_lvs.diode("nmoscl_2", Esd2Term), {"N": nmoscl_2_n, "P": nmoscl_2_p} + ) + + # nmoscl_4 + deck.info("Extracting nmoscl_4 ESD device") + deck.extract_devices( + klayout_lvs.diode("nmoscl_4", Esd2Term), {"N": nmoscl_4_n, "P": nmoscl_4_p} + ) + + # ind_extraction.lvs + deck.info("Starting Inductor EXTRACTION") + + # ind2 + deck.info("Extracting Inductor2 device") + deck.extract_devices( + GeneralNTerminalExtractor("inductor", 2), + { + "core": ind2_core, + "ports": ind2_ports, + "meas_mk": topmetal2.and_(ind2_core), + "dev_mk": ind2_mk, + "sub_mk": ind2_sub, + }, + ) + + # ind3 + deck.info("Extracting Inductor3 device") + deck.extract_devices( + GeneralNTerminalExtractor("inductor3", 3), + { + "core": ind3_core, + "ports": ind3_ports, + "meas_mk": topmetal2.and_(ind3_core), + "dev_mk": ind3_mk, + "sub_mk": ind3_sub, + }, + ) + + # tap_extraction.lvs + deck.info("Starting Taps EXTRACTION") + + # ntap1 + deck.info("Extracting ntap1 device") + deck.extract_devices( + klayout_lvs.diode("ntap1", CustomTap), {"N": ntap1_tie, "P": ntap1_well} + ) + + # ptap1 + deck.info("Extracting ptap1 device") + deck.extract_devices( + klayout_lvs.diode("ptap1", CustomTap), {"N": ptap1_tie, "P": ptap1_sub} + ) + + # ================================================== + # ------------ COMPARISON PREPARATIONS ------------ + # ================================================== + + deck.info("Starting SG13G2 LVS Options Preparations") + + # === Aligns the extracted netlist vs. the schematic === + deck.info("Starting SG13G2 LVS Alignment") + deck.align() + + netlist = deck.netlist() + schematic = deck.schematic + + # === NETLIST OPTIONS === + deck.info("Starting SG13G2 LVS Simplification") + # SIMPLIFY + if SIMPLIFY: + netlist.simplify() + schematic.simplify() + + # TOP_LVL_PINS + if TOP_LVL_PINS: + netlist.make_top_level_pins() + schematic.make_top_level_pins() + + # COMBINE_DEVICES + if COMBINE_DEVICES: + netlist.combine_devices() + schematic.combine_devices() + + # PURGE + if PURGE: + netlist.purge() + schematic.purge() + + # PURGE_NETS + if PURGE_NETS: + netlist.purge_nets() + schematic.purge_nets() + + # === IGNORE EXTREME VALUES === + deck.max_res(1e9) + deck.min_caps(1e-18) + + # === COMPARISON === + deck.info("Starting SG13G2 LVS Comparison") + match = deck.compare() + + # ================================================ + # ------------- COMPARISON RESULTS --------------- + # ================================================ + + if not match: + deck.info("ERROR : Netlists don't match") + else: + deck.info("INFO : Congratulations! Netlists match.") + return match diff --git a/flow/scripts/cdl.tcl b/flow/scripts/cdl.tcl index f45d941ef4..cc92e08279 100644 --- a/flow/scripts/cdl.tcl +++ b/flow/scripts/cdl.tcl @@ -2,4 +2,4 @@ source $::env(SCRIPTS_DIR)/load.tcl erase_non_stage_variables final load_design 6_final.odb 6_final.sdc -write_cdl -masters $::env(CDL_FILE) $::env(RESULTS_DIR)/6_final.cdl +write_cdl -include_fillers -masters $::env(CDL_FILE) $::env(RESULTS_DIR)/6_final.cdl diff --git a/flow/util/klayout_drc.py b/flow/util/klayout_drc.py index 16843e8df3..9861830505 100644 --- a/flow/util/klayout_drc.py +++ b/flow/util/klayout_drc.py @@ -85,6 +85,9 @@ def projection_limits(lo, hi=None): no_limit = _Option("size_mode", 4) padding_zero = _Option("padding", "zero") padding_ignore = _Option("padding", "ignore") +as_boxes = _Option("output_mode", "boxes") +as_dots = _Option("output_mode", "dots") +as_edges = _Option("output_mode", "edges") def tile_size(deck, x, y=None): @@ -297,6 +300,18 @@ def _input(self, layer, datatype, flags, sel, cls=None): r = cls(it, db.ICplxTrans(1.0)) return Layer(self, r) + def extent(self, cell_filter): + """The bounding box of each cell the filter (a glob) matches, in + that cell, as the DRC language's extent(cell_filter).""" + tmp = self.layout.insert_layer(db.LayerInfo()) + for cell in self.layout.cells(cell_filter): + cell.shapes(tmp).insert(cell.bbox()) + it = db.RecursiveShapeIterator(self.layout, self.top, [tmp]) + it.shape_flags = db.Shapes.SAll + if self.dss is not None: + return Layer(self, db.Region(it, self.dss, db.ICplxTrans(1.0))) + return Layer(self, db.Region(it, db.ICplxTrans(1.0))) + def polygon_layer(self): return Layer(self, db.Region()) @@ -319,8 +334,10 @@ def _l2n_register(self, data): def connect(self, a, b): self._l2n_register(a.data) self._l2n_register(b.data) - self.l2n.connect(a.data) - self.l2n.connect(b.data) + if _is_region(a.data): + self.l2n.connect(a.data) + if _is_region(b.data): + self.l2n.connect(b.data) self.l2n.connect(a.data, b.data) def clear_connections(self): @@ -614,6 +631,12 @@ def with_bbox_width(self, *args): def without_bbox_width(self, *args): return self._bbox("bbox_width", True, args) + def with_bbox_max(self, *args): + return self._bbox("bbox_max", False, args) + + def without_bbox_max(self, *args): + return self._bbox("bbox_max", True, args) + def with_bbox_min(self, *args): return self._bbox("bbox_min", False, args) @@ -720,6 +743,19 @@ def corners(self, amin=-180.0, amax=None): self.data.corners(float(amin), float(amax), 1, True, True, False, False) ) + def middle(self, *args): + """The bounding box centers, as 1 x 1 dbu boxes (as_boxes, the + default) or as dots (as_dots, as_edges).""" + dots = False + for a in args: + if isinstance(a, _Option) and a.kind == "output_mode": + dots = a.value in ("dots", "edges") + else: + raise ValueError("invalid middle argument %r" % (a,)) + if dots: + return self._new(self.data.extent_refs_edges(0.5, 0.5, 0.5, 0.5)) + return self._new(self.data.extent_refs(0.5, 0.5, 0.5, 0.5, 1, 1)) + def sized(self, *args): """sized(d), sized(dx, dy), each with a sizing mode option.""" mode = 2 diff --git a/flow/util/klayout_lvs.py b/flow/util/klayout_lvs.py index 839cb1697d..04d7a89567 100644 --- a/flow/util/klayout_lvs.py +++ b/flow/util/klayout_lvs.py @@ -21,12 +21,18 @@ class LvsDeck(klayout_drc.Deck): def __init__(self, in_gds, report_file, cdl_file, top_cell, threads=1): super().__init__(in_gds, report_file, "LVS", mode="deep", threads=threads) self.top_cell = top_cell + self.cdl_file = cdl_file self.scaling = 1.0 self.post_extract = [] self.comparer_config = [] - self.info("Reading netlist: %s .." % cdl_file) + self.read_schematic(db.NetlistSpiceReader()) + + def read_schematic(self, reader): + """Reads the CDL with a reader, as the LVS language's + schematic(file, reader).""" + self.info("Reading netlist: %s .." % self.cdl_file) self.schematic = db.Netlist() - self.schematic.read(cdl_file, db.NetlistSpiceReader()) + self.schematic.read(self.cdl_file, reader) def _l2n_register(self, data): if self.l2n is None: @@ -130,6 +136,22 @@ def f(comparer): self.comparer_config.append(f) + def max_res(self, value): + v = float(value) + + def f(comparer): + comparer.max_resistance = v + + self.comparer_config.append(f) + + def min_caps(self, value): + v = float(value) + + def f(comparer): + comparer.min_capacitance = v + + self.comparer_config.append(f) + def _comparer(self): comparer = db.NetlistComparer() for f in self.comparer_config: @@ -164,8 +186,51 @@ def write(self): self.l2n.write(self.report_file, True) -def mos4(name): - return db.DeviceExtractorMOS4Transistor(name, False, None) +# Device classes and factories handed to the extractors; the klayout +# objects hold them by pointer, so Python keeps them alive. +_keep = [] + + +class _DeviceClassFactory(db.DeviceClassFactory if db else object): + """A device class factory for a class, as the DRC language's + CustomDeviceClassFactory.""" + + def __init__(self, cls): + super().__init__() + self.cls = cls + + def create_class(self): + dc = self.cls() + _keep.append(dc) + return dc + + +def _factory(cls): + if cls is None: + return None + f = _DeviceClassFactory(cls) + _keep.append(f) + return f + + +def mos4(name, cls=None): + return db.DeviceExtractorMOS4Transistor(name, False, _factory(cls)) + + +def bjt3(name, cls=None): + return db.DeviceExtractorBJT3Transistor(name, _factory(cls)) + + +def bjt4(name, cls=None): + return db.DeviceExtractorBJT4Transistor(name, _factory(cls)) + + +def diode(name, cls=None): + return db.DeviceExtractorDiode(name, _factory(cls)) + + +def resistor(name, sheet_rho, cls=None): + return db.DeviceExtractorResistor(name, sheet_rho, _factory(cls)) def parse_args(argv): From 0bcdd7d6f6c667f562445cea616af4dfec264574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 11:43:02 +0100 Subject: [PATCH 08/11] ihp-sg13g2: delete the unused Ruby DRC deck sg13g2_maximal.lydrc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before, ihp-sg13g2 carried sg13g2_maximal.lydrc, a Ruby deck no flow runs. Now it is gone. No flow sets KLAYOUT_DRC_FILE to it; sg13g2_minimal_drc.py is the deck ORFS runs. IHP's own repository keeps the full deck. sg13g2_update.py does not fetch it: its download line is commented out, since IHP replaced the minimal and maximal decks with a parameterizable sg13g2.drc. test_klayout_sh.py named it only as an argument to klayout.sh, which stops before reading it; the test now names a generic deck.lydrc. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018PFLaCzFtFbnWE4nPTDwpC Signed-off-by: Øyvind Harboe --- .../ihp-sg13g2/drc/sg13g2_maximal.lydrc | 4168 ----------------- flow/test/test_klayout_sh.py | 2 +- 2 files changed, 1 insertion(+), 4169 deletions(-) delete mode 100644 flow/platforms/ihp-sg13g2/drc/sg13g2_maximal.lydrc diff --git a/flow/platforms/ihp-sg13g2/drc/sg13g2_maximal.lydrc b/flow/platforms/ihp-sg13g2/drc/sg13g2_maximal.lydrc deleted file mode 100644 index fed5f2b415..0000000000 --- a/flow/platforms/ihp-sg13g2/drc/sg13g2_maximal.lydrc +++ /dev/null @@ -1,4168 +0,0 @@ - - - - - - drc - - - - false - false - - true - drc_scripts - tools_menu.drc.end - dsl - drc-dsl-xml - # Supported variables that can be set using "-rd <name>=<value>" on the command line: -# log_file - path to the log file [default: no log file] -# in_gds - path to the GDS layout to check (required in batch mode) -# cell - name of the cell to check -# report_file - path to the report database [default: sg13g2_maximal.lyrdb in the layout directory] - -# to set logfile: -rd logfile="sg13g2_maximal.log" -if $log_file - log_file($log_file) -end - -application = RBA::Application.instance -main_window = application.main_window -if main_window and not $in_gds - curr_layout_view = main_window.current_view() - unless curr_layout_view - layout_path = RBA::FileDialog::ask_open_file_name("Chose your layout file.", ".", "GDSII files (*.GDS *.gds *.GDS.gz *.gds.gz *.GDS2 *.gds2 *.GDS2.gz *.gds2.gz);; All files (*)") - unless layout_path - return - end - main_window.load_layout(layout_path, 1) - curr_layout_view = main_window.current_view() - end - active_filename = RBA::CellView::active.filename - if active_filename != "" - report_dir = File.dirname(active_filename) - else - report_dir = "/tmp" - end - active_layout = RBA::CellView::active.layout - active_cellname = RBA::CellView::active.cell_name - source(active_layout, active_cellname) -else - log("DRC: batch mode") - # to set input layout: -rd in_gds="path to GDS file" - # to set cell: -rd cell="topcell" - if $cell - active_cellname = $cell - log("Active cell: " + active_cellname) - source($in_gds, active_cellname) - active_layout = source.layout - else - source($in_gds) - active_layout = source.layout - active_cellname = source.cell_name - end - report_dir = File.dirname($in_gds) -end - -unless active_layout or $in_gds - raise("layout file must be defined on command line or via 'in_gds' script variable") -end - -if active_layout.dbu != 0.001 - puts "WARNING: Layout dbu value (" + active_layout.dbu.to_s + " ) deviates from rule file dbu value (0.001). This will scale the layout and may not be intended." -end - -report_dir = File.expand_path(report_dir) -report_file = report_dir + "/sg13g2_maximal.lyrdb" -# to set report file: -rd report_file="sg13g2_maximal.lyrdb" -if $report_file - report_file = File.expand_path($report_file) -end - -report("design rules: sg13g2_maximal | layout cell: " + active_cellname, report_file) - -deep - -if $threads - threads($threads.to_i) -end - -$drc_error_count = 0 - -class DRC::DRCLayer - unless method_defined?(:original_output) - alias_method :original_output, :output - end - - def output(*args) - count = self.hier_count() - $drc_error_count += count - puts("%s: %d" % [args[0], count]) - original_output(*args) - end -end - -# Initial definitions of control flow variables -# Strings from the command line have to be converted -if defined? $offGridRules - $offGridRules = $offGridRules.to_s.downcase == "true" -else - $offGridRules = true -end -if defined? $fillerRules - $fillerRules = $fillerRules.to_s.downcase == "true" -else - $fillerRules = true -end -if defined? $densityRules - $densityRules = $densityRules.to_s.downcase == "true" -else - $densityRules = true -end -if defined? $latchUpRules - $latchUpRules = $latchUpRules.to_s.downcase == "true" -else - $latchUpRules = true -end -if defined? $recommendedRules - $recommendedRules = $recommendedRules.to_s.downcase == "true" -else - $recommendedRules = true -end - -class AbuttingEdges < RBA::EdgePairToEdgeOperator - def initialize - self.is_isotropic_and_scale_invariant - end - - def process(edge_pair) - if edge_pair.first.coincident?(edge_pair.second) - if edge_pair.first.length < edge_pair.second.length - return [ edge_pair.first ] - else - return [ edge_pair.second ] - end - else - return [] - end - end -end - -class IntersectingEdgeErrorFilter < RBA::EdgePairOperator - def initialize(abutting_edges) - @abutting_edges = abutting_edges - end - - def process(edge_pair) - ip = nil - if edge_pair.first.p1 == edge_pair.second.p1 or - edge_pair.first.p1 == edge_pair.second.p2 - ip = edge_pair.first.p1 - elsif edge_pair.first.p2 == edge_pair.second.p1 or - edge_pair.first.p2 == edge_pair.second.p2 - ip = edge_pair.first.p2 - end - if ip and not edge_pair.first.coincident?(edge_pair.second) - edges = RBA::Edges::new([ edge_pair.first, edge_pair.second ]) - region = @abutting_edges.extents(1) - interacting_edges = edges.interacting(region) - if interacting_edges.count == 2 - ep = edge_pair.normalized - v1 = ep.first.swapped_points.d - v2 = ep.second.d - angle = Math.acos((v1*v2)/(v1.length*v2.length))/Math::PI*180 - if angle >= 90 - return [] - end - end - end - return [ edge_pair ] - end -end - -class IntersectingEdgeErrorAngleFilter < RBA::EdgePairOperator - def initialize(min_angle, max_angle, include_min_angle, include_max_angle) - self.is_isotropic_and_scale_invariant - @min_angle = min_angle - @max_angle = max_angle - if include_min_angle - @min_angle -= 1e-6 - else - @min_angle += 1e-6 - end - if include_max_angle - @max_angle += 1e-6 - else - @max_angle -= 1e-6 - end - end - - def process(edge_pair) - ip = nil - if edge_pair.first.p1 == edge_pair.second.p1 or - edge_pair.first.p1 == edge_pair.second.p2 - ip = edge_pair.first.p1 - elsif edge_pair.first.p2 == edge_pair.second.p1 or - edge_pair.first.p2 == edge_pair.second.p2 - ip = edge_pair.first.p2 - end - if ip - ep = edge_pair.normalized - v1 = ep.first.swapped_points.d - v2 = ep.second.d - angle = Math.acos((v1*v2)/(v1.length*v2.length))/Math::PI*180 - if (angle < @min_angle) or (angle > @max_angle) - return [] - end - end - return [ edge_pair ] - end -end - -class DRC::DRCEngine - def find_intersecting_edges_errors(dbu_value, - error_edge_pairs_90, - error_edge_pairs_180, - error_edge_pairs_90_180 = nil, - inverse_error_edge_pairs_90 = nil, - inverse_error_edge_pairs_180 = nil, - options = {}) - consider_intersecting_edges = options.fetch(:consider_intersecting_edges, false) - consider_touch_points = options.fetch(:consider_touch_points, false) - ignore_non_axis_aligned_edges = options.fetch(:ignore_non_axis_aligned_edges, false) - min_angle = options.fetch(:min_angle, 0) - max_angle = options.fetch(:max_angle, 90) - include_min_angle = options.fetch(:include_min_angle, true) - include_max_angle = options.fetch(:include_max_angle, false) - area_of_right_angle = dbu_value**2/2 - errors_ep = RBA::EdgePairs::new() - touch_point_errors_ep = RBA::EdgePairs::new() - abutting_edges_errors_ep = RBA::EdgePairs::new() - intersecting_edges_error_candidates = Hash.new() - no_touch_point_error = Hash.new() - error_edge_pairs_90.data.each do |edge_pair| - ip = nil - if edge_pair.first.p1 == edge_pair.second.p1 or - edge_pair.first.p1 == edge_pair.second.p2 - ip = edge_pair.first.p1 - elsif edge_pair.first.p2 == edge_pair.second.p1 or - edge_pair.first.p2 == edge_pair.second.p2 - ip = edge_pair.first.p2 - end - if ip and !edge_pair.first.is_degenerate? and !edge_pair.second.is_degenerate? - if (edge_pair.first.contains?(edge_pair.second.p1) and - edge_pair.first.contains?(edge_pair.second.p2)) or - (edge_pair.second.contains?(edge_pair.first.p1) and - edge_pair.second.contains?(edge_pair.first.p2)) - no_touch_point_error[ip] = true - end - end - end - if consider_intersecting_edges or consider_touch_points - touch_point_candidates = Hash.new() - touch_point_errors = Hash.new() - if inverse_error_edge_pairs_90 and inverse_error_edge_pairs_180 - (inverse_error_edge_pairs_90 + inverse_error_edge_pairs_180).data.each do |edge_pair| - ip = edge_pair.first.intersection_point(edge_pair.second) - if ip - if touch_point_candidates[ip] - touch_point_errors[ip] = true - touch_point_candidates.delete(ip) - elsif !no_touch_point_error[ip] - touch_point_candidates[ip] = edge_pair - end - end - end - end - touch_point_candidates = Hash.new() - if !error_edge_pairs_90_180 - error_edge_pairs_90_180 = error_edge_pairs_90 + error_edge_pairs_180 - end - error_edge_pairs_90_180.data.each do |edge_pair| - ip = nil - if edge_pair.first.p1 == edge_pair.second.p1 or - edge_pair.first.p1 == edge_pair.second.p2 - ip = edge_pair.first.p1 - elsif edge_pair.first.p2 == edge_pair.second.p1 or - edge_pair.first.p2 == edge_pair.second.p2 - ip = edge_pair.first.p2 - elsif edge_pair.first.intersection_point(edge_pair.second) - abutting_edges_errors_ep.insert(edge_pair) - end - if ip and !edge_pair.first.is_degenerate? and !edge_pair.second.is_degenerate? - intersecting_edges_error_candidates[ip] = edge_pair - if touch_point_errors[ip] - touch_point_errors_ep.insert(edge_pair) - intersecting_edges_error_candidates.delete(ip) - elsif touch_point_candidates[ip] - touch_point_errors_ep.insert(edge_pair) - touch_point_errors_ep.insert(touch_point_candidates[ip]) - touch_point_candidates.delete(ip) - intersecting_edges_error_candidates.delete(ip) - elsif !no_touch_point_error[ip] - touch_point_candidates[ip] = edge_pair - end - end - end - if consider_intersecting_edges - errors_ep = RBA::EdgePairs::new(intersecting_edges_error_candidates.values) - errors_ep.process(IntersectingEdgeErrorAngleFilter::new(min_angle, max_angle, include_min_angle, include_max_angle)) - if min_angle == 0 and include_min_angle - errors_ep = errors_ep + abutting_edges_errors_ep - end - end - end - if ignore_non_axis_aligned_edges - errors_ep = errors_ep.with_angle_both(RBA::Edges::OrthoEdges, false) - end - if consider_touch_points - errors_ep = errors_ep + touch_point_errors_ep - end - return DRC::DRCLayer::new(self, errors_ep) - end -end - -class DRC::DRCLayer - def ext_convert_to_edge_pair_layer() - return self.collect_to_edge_pairs { |edge| - RBA::DEdgePair::new(edge, edge, true) - } - end - - def ext_merged(min_coherence, overlap_count) - args = [min_coherence, overlap_count] - return DRCLayer::new(@engine, @engine._tcmd(self.data, 0, self.data.class, :merged, *args)) - end - - def ext_merge(min_coherence, overlap_count) - args = [min_coherence, overlap_count] - if @engine.is_tiled? - self.data = @engine._tcmd(self.data, 0, self.data.class, :merged, *args) - else - @engine._tcmd(self.data, 0, self.data.class, :merge, *args) - end - return self - end - - def ext_and(other) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - output_layer = self & other - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_with_angle(constraint) - if self.polygons? - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - self_edges = self.edges - self.data.min_coherence = self_min_coherence_state - else - self_edges = self - end - lower_bound = nil - upper_bound = nil - output_layer = nil - constraint.each do |expression| - relation = expression[0] - value = expression[1] - if relation == ">" - lower_bound = value + 1e-6 - elsif relation == "<" - upper_bound = value - elsif relation == "==" - output_layer = self_edges.with_angle(value) - if value > 0 and value < 90 - output_layer += self_edges.with_angle(-value) - end - elsif relation == "!=" - output_layer = self_edges.without_angle(value) - if value > 0 and value < 90 - output_layer += self_edges.without_angle(-value) - end - elsif relation == ">=" - lower_bound = value - elsif relation == "<=" - upper_bound = value + 1e-6 - else - raise "invalid expression" - end - end - if lower_bound or upper_bound - output_layer = self_edges.with_angle(lower_bound, upper_bound) - output_layer += self_edges.with_angle(-upper_bound, -lower_bound) - end - return output_layer - end - - def ext_with_area(constraint) - lower_bound = nil - upper_bound = nil - output_layer = nil - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - constraint.each do |expression| - relation = expression[0] - value = expression[1] - if relation == ">" - lower_bound = value + 1.dbu**2 - elsif relation == "<" - upper_bound = value - elsif relation == "==" - output_layer = self.with_area(value) - elsif relation == "!=" - output_layer = self.without_area(value) - elsif relation == ">=" - lower_bound = value - elsif relation == "<=" - upper_bound = value + 1.dbu**2 - else - raise "invalid expression" - end - end - if lower_bound or upper_bound - output_layer = self.with_area(lower_bound, upper_bound) - end - self.data.min_coherence = self_min_coherence_state - return output_layer - end - - def ext_coincident_part(other, outside: false, inverted: false) - if outside and !inverted and self.polygons? and other.polygons? - return self.separation(other, 1).first_edges - end - if self.polygons? - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - self_edges = self.edges - self.data.min_coherence = self_min_coherence_state - else - self_edges = self - end - if other.polygons? - other_min_coherence_state = other.data.min_coherence? - other.data.min_coherence = true - other_edges = other.edges - other.data.min_coherence = other_min_coherence_state - else - other_edges = other - end - if outside - if inverted - return self_edges.not(self_edges.separation(other_edges, 1).first_edges) - else - return self_edges.separation(other_edges, 1).first_edges - end - else - if inverted - return self_edges.not(other_edges) - else - return self_edges.and(other_edges) - end - end - end - - def ext_coincident_edges(other, outside: false, consider_touch_points: false) - if self.polygons? - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - self_edges = self.edges - self.data.min_coherence = self_min_coherence_state - else - self_edges = self - end - if other.polygons? - other_min_coherence_state = other.data.min_coherence? - other.data.min_coherence = true - other_edges = other.edges - other.data.min_coherence = other_min_coherence_state - else - other_edges = other - end - if outside - if consider_touch_points - return self_edges.not_outside(self_edges.separation(other_edges, 1, @engine.whole_edges).first_edges) - else - return self_edges.not_outside(self_edges.separation(other_edges, 1, @engine.whole_edges, @engine.without_touching_corners).first_edges) - end - else - if consider_touch_points - raise "not implemented" - else - return met1_edges.not_outside(self_edges & other_edges) - end - end - end - - def ext_constraint_satisfied(value, constraint) - output_bool = true - constraint.each do |expression| - if expression[0] == ">" - output_bool = output_bool && (value > expression[1]) - elsif expression[0] == "<" - output_bool = output_bool && (value < expression[1]) - elsif expression[0] == "==" - output_bool = output_bool && (value == expression[1]) - elsif expression[0] == "!=" - output_bool = output_bool && (value != expression[1]) - elsif expression[0] == ">=" - output_bool = output_bool && (value >= expression[1]) - elsif expression[0] == "<=" - output_bool = output_bool && (value <= expression[1]) - else - raise "invalid expression" - end - end - return output_bool - end - - def ext_overlapping(other, constraint = []) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - if other.is_merged? - merged_other = other - else - merged_other = other.ext_merged(true, 0) - end - overlap_filter = @engine.overlapping(merged_other.not_inside(self)) - constraint.each do |expression| - overlap_filter = overlap_filter.public_send(expression[0], expression[1]) - end - output_layer = self.drc(@engine.if_all(overlap_filter, ! @engine.inside(merged_other))) - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_covering(other) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - output_layer = self.covering(other.inside(self)) - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_enclosed_at_intersecting_edges(other, - value, - metric=RBA::Region::Euclidian, - consider_intersecting_edges=false, - consider_touch_points=false, - ignore_non_axis_aligned_edges=false, - min_angle=0, - max_angle=90, - include_min_angle=true, - include_max_angle=false, - polygons=false) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - if metric.is_a?(DRC::DRCMetrics) - metric = metric.value - end - if value.is_a? Float - dbu_value = (value/1.dbu).round - else - dbu_value = value - end - error_edge_pairs_90 = DRC::DRCLayer::new(@engine, - self.data.enclosed_check(other.data, dbu_value, false, metric, 90, 1, nil)) - error_edge_pairs_90_180 = DRC::DRCLayer::new(@engine, - self.data.enclosed_check(other.data, dbu_value, false, metric, 180, nil, nil)) - enclosed_errors = @engine.find_intersecting_edges_errors( - dbu_value, - error_edge_pairs_90, - nil, - error_edge_pairs_90_180, - nil, - nil, - { - consider_intersecting_edges: consider_intersecting_edges, - consider_touch_points: consider_touch_points, - ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges, - min_angle: min_angle, - max_angle: max_angle, - include_min_angle: include_min_angle, - include_max_angle: include_max_angle - } - ) - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - if polygons - enclosed_errors = enclosed_errors.polygons.ext_merge(true, 0) - end - return enclosed_errors - end - - def ext_enclosed(other, - value, - metric: @engine.euclidian, - consider_intersecting_edges: true, - consider_touch_points: true, - consider_overlaps: false, - ignore_non_axis_aligned_edges: false, - inside_edges_are_errors: false, - outside_edges_are_errors: false, - min_angle: 0, - max_angle: 90, - include_min_angle: true, - include_max_angle: false, - polygon_output: false) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - metric = @engine.projection - if consider_overlaps - output_layer = self.enclosed(other, value, metric, - @engine.angle_limit(180), @engine.without_touching_corners) - abutting_edges = output_layer.data.processed(AbuttingEdges::new) - output_layer.data.process(IntersectingEdgeErrorFilter::new(abutting_edges)) - else - angle_limit = max_angle - if include_max_angle - angle_limit += 1e-6 - end - output_layer = self.enclosed(other, value, metric, @engine.angle_limit(angle_limit)) - if !consider_intersecting_edges and !consider_touch_points - output_layer = output_layer.without_distance(0) - if ignore_non_axis_aligned_edges - output_layer = output_layer.with_angle(@engine.ortho, @engine.both) - end - elsif (consider_intersecting_edges ^ consider_touch_points) or - !(min_angle==0 and max_angle==90 and include_min_angle and !include_max_angle) - intersecting_edges_errors = output_layer.with_distance(0).edges - candidate_layer1 = self.interacting(intersecting_edges_errors) - candidate_layer2 = other.interacting(intersecting_edges_errors) - output_layer = output_layer.without_distance(0) - output_layer = output_layer + candidate_layer1.ext_enclosed_at_intersecting_edges( - candidate_layer2, - value, - metric, - consider_intersecting_edges, - consider_touch_points, - ignore_non_axis_aligned_edges, - min_angle, - max_angle, - include_min_angle, - include_max_angle, - false) - end - end - if inside_edges_are_errors - output_layer = output_layer.join(other.edges.inside_part(self).ext_convert_to_edge_pair_layer()) - end - if outside_edges_are_errors - output_layer = output_layer.join(self.edges.not(other).join(self.ext_coincident_part(other, outside: true)).ext_convert_to_edge_pair_layer()) - end - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - if polygon_output - output_layer = output_layer.polygons.ext_merge(true, 0) - end - return output_layer - end - - def ext_encloses_box(l, w, check_rotated: true) - sl = -0.5 * l + 1.dbu - sl = sl > 0.0 ? 0.0 : sl - sw = -0.5 * w + 1.dbu - sw = sw > 0.0 ? 0.0 : sw - if (sl - sw).abs > 1e-6 - r0 = self - r90 = self - if sl < 0.0 - r0 = r0.sized(sl, 0).sized(-sl, 0) - r90 = r90.sized(0, sl).sized(0, -sl) - end - if sw < 0.0 - r0 = r0.sized(0, sw).sized(0, -sw) - r90 = r90.sized(sw, 0).sized(-sw, 0) - end - output_layer = r0.or(r90) - else - output_layer = self.sized(sl, 0).sized(-sl, 0).sized(0, sl).sized(0, -sl) - end - if check_rotated then - rotated = self.rotated(45) - output_layer = output_layer.or(rotated.ext_encloses_box(l, w, check_rotated: false).rotated(-45)) - end - return output_layer - end - - def ext_extended(outside = 0, inside = 0) - if self.polygons? - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - edge_layer = self.edges - self.data.min_coherence = self_min_coherence_state - else - edge_layer = self - end - output_layer = edge_layer.extended(:out => outside, :in => inside) - return output_layer.ext_merge(true, 0) - end - - def ext_extents - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - output_layer = self.extents.ext_merge(true, 0) - self.data.min_coherence = self_min_coherence_state - return output_layer - end - - def ext_separation_at_intersecting_edges(other, - value, - metric=RBA::Region::Euclidian, - consider_intersecting_edges=false, - consider_touch_points=false, - ignore_non_axis_aligned_edges=false, - min_angle=0, - max_angle=90, - include_min_angle=true, - include_max_angle=false, - polygons=false) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - if metric.is_a?(DRC::DRCMetrics) - metric = metric.value - end - if value.is_a? Float - dbu_value = (value/1.dbu).round - else - dbu_value = value - end - error_edge_pairs_90 = DRC::DRCLayer::new(@engine, - self.data.separation_check(other.data, dbu_value, false, metric, 90, 1, nil)) - error_edge_pairs_90_180 = DRC::DRCLayer::new(@engine, - self.data.separation_check(other.data, dbu_value, false, metric, 180)) - width_error_edge_pairs_90 = DRC::DRCLayer::new(@engine, - self.data.width_check(dbu_value, false, metric, 90, 1, nil) + - other.data.width_check(dbu_value, false, metric, 90, 1, nil)) - width_error_edge_pairs_180 = DRC::DRCLayer::new(@engine, - self.data.width_check(dbu_value, false, metric, 180, nil, 1) + - other.data.width_check(dbu_value, false, metric, 180, nil, 1)) - separation_errors = @engine.find_intersecting_edges_errors( - dbu_value, - error_edge_pairs_90, - nil, - error_edge_pairs_90_180, - width_error_edge_pairs_90, - width_error_edge_pairs_180, - { - consider_intersecting_edges: consider_intersecting_edges, - consider_touch_points: consider_touch_points, - ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges, - min_angle: min_angle, - max_angle: max_angle, - include_min_angle: include_min_angle, - include_max_angle: include_max_angle - } - ) - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - if polygons - separation_errors = separation_errors.polygons.ext_merge(true, 0) - end - return separation_errors - end - - def ext_separation(other, - value, - metric: @engine.euclidian, - consider_intersecting_edges: true, - consider_touch_points: true, - consider_overlaps: false, - ignore_non_axis_aligned_edges: false, - inside_edges_are_errors: false, - min_angle: 0, - max_angle: 90, - include_min_angle: true, - include_max_angle: false, - polygon_output: false) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - if consider_overlaps - output_layer = self.separation(other, value, metric, - @engine.angle_limit(180), @engine.without_touching_corners) - abutting_edges = output_layer.data.processed(AbuttingEdges::new) - output_layer.data.process(IntersectingEdgeErrorFilter::new(abutting_edges)) - else - angle_limit = max_angle - if include_max_angle - angle_limit += 1e-6 - end - output_layer = self.separation(other, value, metric, @engine.angle_limit(angle_limit)) - if !consider_intersecting_edges and !consider_touch_points - output_layer = output_layer.without_distance(0) - if ignore_non_axis_aligned_edges - output_layer = output_layer.with_angle(@engine.ortho, @engine.both) - end - elsif (consider_intersecting_edges ^ consider_touch_points) or - !(min_angle==0 and max_angle==90 and include_min_angle and !include_max_angle) - intersecting_edges_errors = output_layer.with_distance(0).edges - candidate_layer1 = self.interacting(intersecting_edges_errors) - candidate_layer2 = other.interacting(intersecting_edges_errors) - output_layer = output_layer.without_distance(0) - output_layer = output_layer + candidate_layer1.ext_separation_at_intersecting_edges( - candidate_layer2, - value, - metric, - consider_intersecting_edges, - consider_touch_points, - ignore_non_axis_aligned_edges, - min_angle, - max_angle, - include_min_angle, - include_max_angle, - false) - end - end - if inside_edges_are_errors - output_layer = output_layer.join(self.and(other).edges.ext_convert_to_edge_pair_layer()) - end - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - if polygon_output - output_layer = output_layer.polygons.ext_merge(true, 0) - end - return output_layer - end - - def ext_inside_part(other, inverted: false) - if self.polygons? - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - edge_layer = self.edges - self.data.min_coherence = self_min_coherence_state - else - edge_layer = self - end - merged_other = other.ext_merged(true, 0) - if inverted - return edge_layer.outside_part(merged_other) - else - return edge_layer.inside_part(merged_other) - end - end - - def ext_interacting(other, constraint=nil, inverted: false) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - overlap_filter = @engine.secondary(other).overlapping(@engine.primary) - if not constraint - touch_filter = @engine.secondary(other).outside(@engine.primary).edges & @engine.primary - if inverted - output_layer = self.drc(@engine.if_none(overlap_filter, touch_filter)) - else - output_layer = self.drc(@engine.if_any(overlap_filter, touch_filter)) - end - else - touch_filter = (@engine.secondary(other).outside(@engine.primary).edges & @engine.primary).polygons(0.1.um).merged - filter = (overlap_filter + touch_filter).count - constraint.each do |expression| - filter = filter.public_send(expression[0], expression[1]) - end - if inverted - output_layer = self.drc(@engine.if_none(filter)) - else - output_layer = self.drc(@engine.if_any(filter)) - end - end - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_overlap_at_intersecting_edges(other, - value, - metric=RBA::Region::Euclidian, - consider_intersecting_edges=false, - consider_touch_points=false, - ignore_non_axis_aligned_edges=false, - min_angle=0, - max_angle=90, - include_min_angle=true, - include_max_angle=false, - polygons=false) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - if metric.is_a?(DRC::DRCMetrics) - metric = metric.value - end - if value.is_a? Float - dbu_value = (value/1.dbu).round - else - dbu_value = value - end - error_edge_pairs_90 = DRC::DRCLayer::new(@engine, - self.data.overlap_check(other.data, dbu_value, false, metric, 90, 1, nil)) - error_edge_pairs_180 = DRC::DRCLayer::new(@engine, - self.data.overlap_check(other.data, dbu_value, false, metric, 180, nil, 1)) - overlap_errors = @engine.find_intersecting_edges_errors( - dbu_value, - error_edge_pairs_90, - error_edge_pairs_180, - nil, - nil, - nil, - { - consider_intersecting_edges: consider_intersecting_edges, - consider_touch_points: consider_touch_points, - ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges, - min_angle: min_angle, - max_angle: max_angle, - include_min_angle: include_min_angle, - include_max_angle: include_max_angle - } - ) - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - if polygons - overlap_errors = overlap_errors.polygons.ext_merge(true, 0) - end - return overlap_errors - end - - def ext_overlap(other, - value, - metric: @engine.euclidian, - consider_intersecting_edges: true, - consider_touch_points: true, - ignore_non_axis_aligned_edges: false, - min_angle: 0, - max_angle: 90, - include_min_angle: true, - include_max_angle: false, - polygon_output: false) - if self.polygons? - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - end - if other.polygons? - other_min_coherence_state = other.data.min_coherence? - other.data.min_coherence = true - end - if self.polygons? and other.polygons? - output_layer = self.overlap(other, value) - else - if self.polygons? - self_edges = self.edges - else - self_edges = self - end - if other.polygons? - other_edges = other.edges - else - other_edges = other - end - output_layer = self_edges.overlap(other_edges, value) - end - if !consider_intersecting_edges and !consider_touch_points - output_layer = output_layer.with_distance(1, nil) - if ignore_non_axis_aligned_edges - output_layer = output_layer.with_angle(@engine.ortho, @engine.both) - end - elsif consider_intersecting_edges ^ consider_touch_points - intersecting_edges_errors = output_layer.with_distance(0).edges - candidate_layer1 = self.interacting(intersecting_edges_errors) - candidate_layer2 = other.interacting(intersecting_edges_errors) - output_layer = output_layer.with_distance(1, nil) - output_layer = output_layer + candidate_layer1.ext_overlap_at_intersecting_edges( - candidate_layer2, - value, - metric, - consider_intersecting_edges, - consider_touch_points, - ignore_non_axis_aligned_edges, - min_angle, - max_angle, - include_min_angle, - include_max_angle, - false) - end - if self.polygons? - self.data.min_coherence = self_min_coherence_state - end - if other.polygons? - other.data.min_coherence = other_min_coherence_state - end - if polygon_output - output_layer = output_layer.polygons.ext_merge(true, 0) - end - return output_layer - end - - def ext_with_coincident_edges(other) - coincident_edges = self.edges & other - return self.interacting(coincident_edges) - end - - def ext_with_length(constraint) - if self.polygons? - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - edge_layer = self.edges - self.data.min_coherence = self_min_coherence_state - else - edge_layer = self.dup - end - constraint.each do |expression| - if expression[0] == ">" - edge_layer = edge_layer.with_length((expression[1] + 1), nil) - elsif expression[0] == "<" - edge_layer = edge_layer.with_length(nil, expression[1]) - elsif expression[0] == "==" - edge_layer = edge_layer.with_length(expression[1]) - elsif expression[0] == "!=" - edge_layer = edge_layer.without_length(expression[1]) - elsif expression[0] == ">=" - edge_layer = edge_layer.with_length(expression[1], nil) - elsif expression[0] == "<=" - edge_layer = edge_layer.with_length(nil, (expression[1] + 1)) - else - raise "invalid expression" - end - end - return edge_layer - end - - def ext_not(other) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - output_layer = self - other - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_or(other, *further_layers) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - output_layer = self.join(other) - further_layers.each do |further_layer| - further_layer_min_coherence_state = further_layer.data.min_coherence? - further_layer.data.min_coherence = true - output_layer = output_layer.join(further_layer) - further_layer.data.min_coherence = further_layer_min_coherence_state - end - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_rectangles(axis_aligned = false, use_bbox = false, constraint1 = nil, constraint2 = nil, aspect_ratio_constraint = nil, inverted: false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - if ( ( constraint1 && ( !constraint2 || constraint1.length() > 1 || constraint1[0][0] != "==") ) || - ( constraint2 && ( constraint2.length() > 1 || constraint2[0][0] != "==" ) ) || - ( constraint1 && constraint2 && constraint1[0][1] != constraint2[0][1] ) ) - raise "ext_rectangle: unsupported options" - end - square = constraint1 ? true : false - shape_filter = - if use_bbox - @engine.extents - elsif axis_aligned - @engine.rectangles - else - @engine.if_all((@engine.corners == 270).count == 4, @engine.corners.count == 4) - end - if square - if use_bbox - shape_filter = @engine.if_all((@engine.extents.length == constraint1[0][1]).count == 4) - else - square_filter = (@engine.length == constraint1[0][1]).count == 4 - shape_filter = @engine.if_all(shape_filter, square_filter) - end - end - if inverted - output_layer = self.drc(! shape_filter) - else - output_layer = self.drc(shape_filter) - end - self.data.min_coherence = self_min_coherence_state - return output_layer - end - - def ext_space_at_intersecting_edges(value, - metric=RBA::Region::Euclidian, - consider_intersecting_edges=false, - consider_touch_points=false, - ignore_non_axis_aligned_edges=false, - min_angle=0, - max_angle=90, - include_min_angle=true, - include_max_angle=false, - polygons=false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - if metric.is_a?(DRC::DRCMetrics) - metric = metric.value - end - if value.is_a? Float - dbu_value = (value/1.dbu).round - else - dbu_value = value - end - error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.space_check(dbu_value, false, metric, 90, 1, nil)) - error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.space_check(dbu_value, false, metric, 180, nil, 1)) - width_error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.width_check(dbu_value, false, metric, 90, 1, nil)) - width_error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.width_check(dbu_value, false, metric, 180, nil, 1)) - space_errors = @engine.find_intersecting_edges_errors( - dbu_value, - error_edge_pairs_90, - error_edge_pairs_180, - nil, - width_error_edge_pairs_90, - width_error_edge_pairs_180, - { - consider_intersecting_edges: consider_intersecting_edges, - consider_touch_points: consider_touch_points, - ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges, - min_angle: min_angle, - max_angle: max_angle, - include_min_angle: include_min_angle, - include_max_angle: include_max_angle - } - ) - self.data.min_coherence = self_min_coherence_state - if polygons - space_errors = space_errors.polygons.ext_merge(true, 0) - end - return space_errors - end - - def ext_space(value, - metric: @engine.euclidian, - consider_intersecting_edges: true, - consider_touch_points: true, - ignore_non_axis_aligned_edges: false, - min_angle: 0, - max_angle: 90, - include_min_angle: true, - include_max_angle: false, - polygon_output: false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - output_layer = self.space(value, metric, @engine.angle_limit(max_angle)) - if !consider_intersecting_edges and !consider_touch_points - output_layer = output_layer.with_distance(1, nil) - if ignore_non_axis_aligned_edges - output_layer = output_layer.with_angle(@engine.ortho, @engine.both) - end - elsif consider_intersecting_edges ^ consider_touch_points - candidate_layer = self.interacting(output_layer.with_distance(0).edges) - output_layer = output_layer.with_distance(1, nil) - output_layer = output_layer + candidate_layer.ext_space_at_intersecting_edges( - value, - metric, - consider_intersecting_edges, - consider_touch_points, - ignore_non_axis_aligned_edges, - min_angle, - max_angle, - include_min_angle, - include_max_angle, - false) - end - self.data.min_coherence = self_min_coherence_state - if polygon_output - output_layer = output_layer.polygons.ext_merge(true, 0) - end - return output_layer - end - - def ext_width_at_intersecting_edges(value, - metric=RBA::Region::Euclidian, - consider_intersecting_edges=false, - consider_touch_points=false, - ignore_non_axis_aligned_edges=false, - min_angle=0, - max_angle=90, - include_min_angle=true, - include_max_angle=false, - polygons=false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = true - if metric.is_a?(DRC::DRCMetrics) - metric = metric.value - end - if value.is_a? Float - dbu_value = (value/1.dbu).round - else - dbu_value = value - end - error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.width_check(dbu_value, false, metric, 90, 1, nil)) - error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.width_check(dbu_value, false, metric, 180, nil, 1)) - space_error_edge_pairs_90 = DRC::DRCLayer::new(@engine, self.data.space_check(dbu_value, false, metric, 90, 1, nil)) - space_error_edge_pairs_180 = DRC::DRCLayer::new(@engine, self.data.space_check(dbu_value, false, metric, 180, nil, 1)) - width_errors = @engine.find_intersecting_edges_errors( - dbu_value, - error_edge_pairs_90, - error_edge_pairs_180, - nil, - space_error_edge_pairs_90, - space_error_edge_pairs_180, - { - consider_intersecting_edges: consider_intersecting_edges, - consider_touch_points: consider_touch_points, - ignore_non_axis_aligned_edges: ignore_non_axis_aligned_edges, - min_angle: min_angle, - max_angle: max_angle, - include_min_angle: include_min_angle, - include_max_angle: include_max_angle - } - ) - self.data.min_coherence = self_min_coherence_state - if polygons - width_errors = width_errors.polygons.ext_merge(true, 0) - end - return width_errors - end - - def ext_width(value, - metric: @engine.euclidian, - consider_intersecting_edges: true, - consider_touch_points: true, - ignore_non_axis_aligned_edges: false, - min_angle: 0, - max_angle: 90, - include_min_angle: true, - include_max_angle: false, - polygon_output: false) - self_min_coherence_state = self.data.min_coherence? - self.data.min_coherence = false - output_layer = self.width(value, metric, @engine.angle_limit(max_angle)) - if !consider_intersecting_edges and !consider_touch_points - output_layer = output_layer.with_distance(1, nil) - if ignore_non_axis_aligned_edges - output_layer = output_layer.with_angle(@engine.ortho, @engine.both) - end - elsif consider_intersecting_edges ^ consider_touch_points - candidate_layer = self.interacting(output_layer.with_distance(0).edges) - output_layer = output_layer.with_distance(1, nil) - output_layer = output_layer + candidate_layer.ext_width_at_intersecting_edges( - value, - metric, - consider_intersecting_edges, - consider_touch_points, - ignore_non_axis_aligned_edges, - min_angle, - max_angle, - include_min_angle, - include_max_angle, - false) - end - if polygon_output - output_layer = output_layer.polygons.ext_merge(true, 0) - end - self.data.min_coherence = self_min_coherence_state - return output_layer - end - - def ext_enlarge_inside(other, distance, step) - enlarged_layer = self.dup - num_steps = (distance / step + 0.5).to_i - for i in 1..num_steps - enlarged_layer = enlarged_layer.sized(step, @engine.acute_limit) & other - end - rest = distance - num_steps * step - if rest > 1.dbu - enlarged_layer = enlarged_layer.sized(rest, @engine.acute_limit) & other - end - return enlarged_layer - end - - def ext_touching(other, constraint = [[">", 0]]) - self_min_coherence_state = self.data.min_coherence? - other_min_coherence_state = other.data.min_coherence? - self.data.min_coherence = true - other.data.min_coherence = true - outside_filter = @engine.outside(other) - touch_filter = @engine.primary.edges & @engine.secondary(other) - if constraint - touch_filter = touch_filter.count - constraint.each do |expression| - touch_filter = touch_filter.public_send(expression[0], expression[1]) - end - end - output_layer = self.drc(@engine.if_all(outside_filter, touch_filter)) - self.data.min_coherence = self_min_coherence_state - other.data.min_coherence = other_min_coherence_state - return output_layer - end - - def ext_interacting_with_text(text_layer, text) - if text_layer.is_a? Integer - text_layer = @engine.labels(text_layer) - end - initial_merged_semantics = self.data.merged_semantics? - self.data.merged_semantics = false - result = self.interacting(text_layer.texts(text)) - self.data.merged_semantics = initial_merged_semantics - return result - end - - def _ext_with_density(inverse, - range, - *args, - tiles_stay_inside: false) - if inverse - method = :without_density - else - method = :with_density - end - if self.is_merged? - merged_layer = self - else - merged_layer = self.ext_merged(true, 0) - end - origin = 'cc' - tile_size = nil - tile_step = nil - arguments = [range] - args.each do |a| - if a.is_a?(DRC::DRCTileSize) - tile_size = a - arguments.push(tile_size) - elsif a.is_a?(DRC::DRCTileStep) - tile_step = a - arguments.push(tile_step) - elsif a.is_a?(String) - origin = a - else - raise "argument error" - end - end - readonly_extent = DRC::DRCLayer::new(@engine, RBA::Region::new(@engine.source.cell_obj.bbox)) - bbox = readonly_extent.bbox - if origin == 'll' - origin_x = bbox.left - origin_y = bbox.bottom - if tile_size and tile_step and (tile_size.get[0] != tile_step.get[0] or tile_size.get[1] != tile_step.get[1]) - origin_x = bbox.left + (tile_size.get[0]-tile_step.get[0])/2 - origin_y = bbox.bottom + (tile_size.get[1]-tile_step.get[1])/2 - end - tile_origin = DRC::DRCTileOrigin::new(origin_x, origin_y) - arguments.push(tile_origin) - elsif origin == 'cc' - origin_x = bbox.center.x - origin_y = bbox.center.y - tile_origin = DRC::DRCTileOrigin::new(origin_x, origin_y) - else - raise "Unknown origin: 'cc' or 'll' expected" - end - if tile_size - boundary_layer = DRC::DRCLayer::new(@engine, RBA::Region::new(bbox.to_itype(@engine.dbu))) - tile_boundary = DRC::DRCTileBoundary::new(boundary_layer) - if tiles_stay_inside - if origin != 'll' - raise "Option 'tiles_stay_inside' requires origin='ll'" - end - tile_width = tile_size.get[0] - tile_height = tile_size.get[1] - enlarged_bbox = bbox.dup - if tile_width > bbox.width - enlarged_bbox.right = bbox.left + tile_width - end - if tile_height > bbox.height - enlarged_bbox.top = bbox.bottom + tile_height - end - enlarged_boundary_layer = DRC::DRCLayer::new(@engine, RBA::Region::new(enlarged_bbox.to_itype(@engine.dbu))) - if tile_step - tile_step_x = tile_step.get[0] - tile_step_y = tile_step.get[1] - else - tile_step_x = tile_width - tile_step_y = tile_height - end - tile_count_x = 1 + ((bbox.width - tile_width) / tile_step_x).floor() - tile_count_x = tile_count_x < 1 ? 1 : tile_count_x - tile_count_y = 1 + ((bbox.height - tile_height) / tile_step_y).floor() - tile_count_y = tile_count_y < 1 ? 1 : tile_count_y - if tile_count_x == 1 and tile_count_y == 1 - tile_count = DRC::DRCTileCount::new(tile_count_x, 2) - else - tile_count = DRC::DRCTileCount::new(tile_count_x, tile_count_y) - end - result = merged_layer.public_send(method, *arguments, tile_count, tile_boundary, @engine.padding_ignore) - need_top_row = false - need_right_column = false - if bbox.height > tile_height + (tile_count_y-1)*tile_step_y - need_top_row = true - tile_origin2 = DRC::DRCTileOrigin::new(tile_origin.get[0], tile_origin.get[1]+bbox.height-tile_height) - tile_count = DRC::DRCTileCount::new([2, tile_count_x].max, 1) - result += merged_layer.public_send(method, *arguments, tile_origin2, tile_count, tile_boundary, @engine.padding_ignore) - end - if bbox.width > tile_width + (tile_count_x-1)*tile_step_x - need_right_column = true - tile_origin2 = DRC::DRCTileOrigin::new(tile_origin.get[0]+bbox.width-tile_width, tile_origin.get[1]) - tile_count = DRC::DRCTileCount::new(1, [2, tile_count_y].max) - result += merged_layer.public_send(method, *arguments, tile_origin2, tile_count, tile_boundary, @engine.padding_ignore) - end - if (need_top_row and bbox.width > tile_width) or (need_right_column and bbox.height > tile_height) - tile_origin2 = DRC::DRCTileOrigin::new(tile_origin.get[0]+bbox.width-tile_width, tile_origin.get[1]+bbox.height-tile_height) - tile_count = DRC::DRCTileCount::new(1, 2) - result += merged_layer.public_send(method, *arguments, tile_origin2, tile_count, tile_boundary, @engine.padding_ignore) - end - result.raw.select_inside(enlarged_boundary_layer) - else - result = merged_layer.public_send(method, *arguments, tile_boundary, @engine.padding_ignore) - end - return result.and(boundary_layer) - else - tile_size = DRC::DRCTileSize::new(bbox.width, bbox.height) - tile_count = DRC::DRCTileCount::new(1,3) - enlarged_bbox = bbox.enlarged(1.1).to_itype(@engine.dbu) - boundary_layer = DRC::DRCLayer::new(@engine, RBA::Region::new(enlarged_bbox)) - tile_boundary = DRC::DRCTileBoundary::new(boundary_layer) - result = merged_layer.public_send(method, *arguments, tile_size, tile_count, tile_boundary, @engine.padding_ignore) - return result.raw.overlapping(DRC::DRCLayer::new(@engine, RBA::Region::new(bbox.to_itype(@engine.dbu)))) - end - end - - def ext_with_density(*args, **kargs) - self._ext_with_density(false, *args, **kargs) - end - - def ext_without_density(*args, **kargs) - self._ext_with_density(true, *args, **kargs) - end - - def ext_outside(other) - output_layer = self.dup.raw.outside(other) - output_layer.data.merged_semantics = true - return output_layer - end -end - -$start_time = Time.now - -Activ = source.polygons("1/0") -Activ_pin = source.polygons("1/2") -Activ_mask = source.polygons("1/20") -Activ_filler = source.polygons("1/22") -Activ_nofill = source.polygons("1/23") -Activ_OPC = source.polygons("1/26") -Activ_iOPC = source.polygons("1/27") -Activ_noqrc = source.polygons("1/28") -BiWind = source.polygons("3/0") -BiWind_OPC = source.polygons("3/26") -GatPoly = source.polygons("5/0") -GatPoly_pin = source.polygons("5/2") -GatPoly_filler = source.polygons("5/22") -GatPoly_nofill = source.polygons("5/23") -GatPoly_OPC = source.polygons("5/26") -GatPoly_iOPC = source.polygons("5/27") -GatPoly_noqrc = source.polygons("5/28") -Cont = source.polygons("6/0") -Cont_OPC = source.polygons("6/26") -nSD = source.polygons("7/0") -nSD_block = source.polygons("7/21") -Metal1 = source.polygons("8/0") -Metal1_pin = source.polygons("8/2") -Metal1_mask = source.polygons("8/20") -Metal1_filler = source.polygons("8/22") -Metal1_nofill = source.polygons("8/23") -Metal1_slit = source.polygons("8/24") -Metal1_text = source.labels("8/25") -Metal1_OPC = source.polygons("8/26") -Metal1_noqrc = source.polygons("8/28") -Metal1_res = source.polygons("8/29") -Metal1_iprobe = source.polygons("8/33") -Metal1_diffprb = source.polygons("8/34") -Passiv = source.polygons("9/0") -Passiv_pin = source.polygons("9/2") -Passiv_sbump = source.polygons("9/36") -Passiv_pillar = source.polygons("9/35") -Passiv_pdl = source.polygons("9/40") -Metal2 = source.polygons("10/0") -Metal2_pin = source.polygons("10/2") -Metal2_mask = source.polygons("10/20") -Metal2_filler = source.polygons("10/22") -Metal2_nofill = source.polygons("10/23") -Metal2_slit = source.polygons("10/24") -Metal2_text = source.labels("10/25") -Metal2_OPC = source.polygons("10/26") -Metal2_noqrc = source.polygons("10/28") -Metal2_res = source.polygons("10/29") -Metal2_iprobe = source.polygons("10/33") -Metal2_diffprb = source.polygons("10/34") -BasPoly = source.polygons("13/0") -BasPoly_pin = source.polygons("13/2") -pSD = source.polygons("14/0") -NLDB = source.polygons("15/0") -DigiBnd = source.polygons("16/0") -Via1 = source.polygons("19/0") -BackMetal1 = source.polygons("20/0") -BackMetal1_pin = source.polygons("20/2") -BackMetal1_mask = source.polygons("20/20") -BackMetal1_filler = source.polygons("20/22") -BackMetal1_nofill = source.polygons("20/23") -BackMetal1_slit = source.polygons("20/24") -BackMetal1_text = source.labels("20/25") -BackMetal1_OPC = source.polygons("20/26") -BackMetal1_noqrc = source.polygons("20/28") -BackMetal1_res = source.polygons("20/29") -BackMetal1_iprobe = source.polygons("20/33") -BackMetal1_diffprb = source.polygons("20/34") -BackPassiv = source.polygons("23/0") -RES = source.polygons("24/0") -SRAM = source.polygons("25/0") -TRANS = source.polygons("26/0") -IND = source.polygons("27/0") -IND_pin = source.polygons("27/2") -IND_text = source.labels("27/25") -SalBlock = source.polygons("28/0") -Via2 = source.polygons("29/0") -Metal3 = source.polygons("30/0") -Metal3_pin = source.polygons("30/2") -Metal3_mask = source.polygons("30/20") -Metal3_filler = source.polygons("30/22") -Metal3_nofill = source.polygons("30/23") -Metal3_slit = source.polygons("30/24") -Metal3_text = source.labels("30/25") -Metal3_OPC = source.polygons("30/26") -Metal3_noqrc = source.polygons("30/28") -Metal3_res = source.polygons("30/29") -Metal3_iprobe = source.polygons("30/33") -Metal3_diffprb = source.polygons("30/34") -NWell = source.polygons("31/0") -NWell_pin = source.polygons("31/2") -nBuLay = source.polygons("32/0") -nBuLay_pin = source.polygons("32/2") -nBuLay_block = source.polygons("32/21") -EmWind = source.polygons("33/0") -EmWind_OPC = source.polygons("33/26") -DeepCo = source.polygons("35/0") -MIM = source.polygons("36/0") -EdgeSeal = source.polygons("39/0") -Substrate = source.polygons("40/0") -Substrate_text = source.labels("40/25") -dfpad = source.polygons("41/0") -dfpad_pillar = source.polygons("41/35") -dfpad_sbump = source.polygons("41/36") -ThickGateOx = source.polygons("44/0") -PLDB = source.polygons("45/0") -PWell = source.polygons("46/0") -PWell_pin = source.polygons("46/2") -PWell_block = source.polygons("46/21") -IC = source.polygons("48/0") -Via3 = source.polygons("49/0") -Metal4 = source.polygons("50/0") -Metal4_pin = source.polygons("50/2") -Metal4_mask = source.polygons("50/20") -Metal4_filler = source.polygons("50/22") -Metal4_nofill = source.polygons("50/23") -Metal4_slit = source.polygons("50/24") -Metal4_text = source.labels("50/25") -Metal4_OPC = source.polygons("50/26") -Metal4_noqrc = source.polygons("50/28") -Metal4_res = source.polygons("50/29") -Metal4_iprobe = source.polygons("50/33") -Metal4_diffprb = source.polygons("50/34") -HeatTrans = source.polygons("51/0") -HeatRes = source.polygons("52/0") -FBE = source.polygons("54/0") -EmPoly = source.polygons("55/0") -DigiSub = source.polygons("60/0") -NoDRC = source.polygons("62/0") -TEXT = source.labels("63/0") -Via4 = source.polygons("66/0") -Metal5 = source.polygons("67/0") -Metal5_pin = source.polygons("67/2") -Metal5_mask = source.polygons("67/20") -Metal5_filler = source.polygons("67/22") -Metal5_nofill = source.polygons("67/23") -Metal5_slit = source.polygons("67/24") -Metal5_text = source.labels("67/25") -Metal5_OPC = source.polygons("67/26") -Metal5_noqrc = source.polygons("67/28") -Metal5_res = source.polygons("67/29") -Metal5_iprobe = source.polygons("67/33") -Metal5_diffprb = source.polygons("67/34") -RadHard = source.polygons("68/0") -MemCap = source.polygons("69/0") -Varicap = source.polygons("70/0") -IntBondVia = source.polygons("72/0") -IntBondMet = source.polygons("73/0") -DevBondVia = source.polygons("74/0") -DevBondMet = source.polygons("75/0") -DevTrench = source.polygons("76/0") -Redist = source.polygons("77/0") -GraphBot = source.polygons("78/0") -GraphTop = source.polygons("79/0") -AntVia1 = source.polygons("83/0") -AntMetal2 = source.polygons("84/0") -GraphCont = source.polygons("85/0") -SiWG = source.polygons("86/0") -SiWG_filler = source.polygons("86/22") -SiWG_nofill = source.polygons("86/23") -SiGrating = source.polygons("87/0") -SiNGrating = source.polygons("88/0") -GraphPas = source.polygons("89/0") -EmWind3 = source.polygons("90/0") -EmWiHV3 = source.polygons("91/0") -RedBuLay = source.polygons("92/0") -SMOS = source.polygons("93/0") -GraphPad = source.polygons("97/0") -Polimide = source.polygons("98/0") -Polimide_pin = source.polygons("98/2") -Recog = source.polygons("99/0") -Recog_pin = source.polygons("99/2") -Recog_esd = source.polygons("99/30") -Recog_diode = source.polygons("99/31") -Recog_tsv = source.polygons("99/32") -Recog_iprobe = source.polygons("99/33") -Recog_diffprb = source.polygons("99/34") -Recog_pillar = source.polygons("99/35") -Recog_sbump = source.polygons("99/36") -Recog_otp = source.polygons("99/37") -Recog_pdiode = source.polygons("99/38") -Recog_mom = source.polygons("99/39") -Recog_pcm = source.polygons("99/100") -ColOpen = source.polygons("101/0") -GraphMetal1 = source.polygons("109/0") -GraphMetal1_filler = source.polygons("109/22") -GraphMetal1_nofill = source.polygons("109/23") -GraphMetal1_slit = source.polygons("109/24") -GraphMetal1_OPC = source.polygons("109/26") -GraphMet1L = source.polygons("110/0") -GraphMet1L_filler = source.polygons("110/22") -GraphMet1L_nofill = source.polygons("110/23") -GraphMet1L_slit = source.polygons("110/24") -GraphMet1L_OPC = source.polygons("110/26") -EXTBlock = source.polygons("111/0") -NLDD = source.polygons("112/0") -PLDD = source.polygons("113/0") -NExt = source.polygons("114/0") -PExt = source.polygons("115/0") -NExtHV = source.polygons("116/0") -PExtHV = source.polygons("117/0") -GraphGate = source.polygons("118/0") -SiNWG = source.polygons("119/0") -SiNWG_filler = source.polygons("119/22") -SiNWG_nofill = source.polygons("119/23") -MEMPAD = source.polygons("124/0") -TopVia1 = source.polygons("125/0") -TopMetal1 = source.polygons("126/0") -TopMetal1_pin = source.polygons("126/2") -TopMetal1_mask = source.polygons("126/20") -TopMetal1_filler = source.polygons("126/22") -TopMetal1_nofill = source.polygons("126/23") -TopMetal1_slit = source.polygons("126/24") -TopMetal1_text = source.labels("126/25") -TopMetal1_noqrc = source.polygons("126/28") -TopMetal1_res = source.polygons("126/29") -TopMetal1_iprobe = source.polygons("126/33") -TopMetal1_diffprb = source.polygons("126/34") -INLDPWL = source.polygons("127/0") -PolyRes = source.polygons("128/0") -PolyRes_pin = source.polygons("128/2") -Vmim = source.polygons("129/0") -nBuLayCut = source.polygons("131/0") -AntMetal1 = source.polygons("132/0") -TopVia2 = source.polygons("133/0") -TopMetal2 = source.polygons("134/0") -TopMetal2_pin = source.polygons("134/2") -TopMetal2_mask = source.polygons("134/20") -TopMetal2_filler = source.polygons("134/22") -TopMetal2_nofill = source.polygons("134/23") -TopMetal2_slit = source.polygons("134/24") -TopMetal2_text = source.labels("134/25") -TopMetal2_noqrc = source.polygons("134/28") -TopMetal2_res = source.polygons("134/29") -TopMetal2_iprobe = source.polygons("134/33") -TopMetal2_diffprb = source.polygons("134/34") -SNSRing = source.polygons("135/0") -Sensor = source.polygons("136/0") -SNSArms = source.polygons("137/0") -SNSCMOSVia = source.polygons("138/0") -ColWind = source.polygons("139/0") -FLM = source.polygons("142/0") -HafniumOx = source.polygons("143/0") -MEMVia = source.polygons("145/0") -ThinFilmRes = source.polygons("146/0") -RFMEM = source.polygons("147/0") -NoRCX = source.polygons("148/0") -NoRCX_m2m3 = source.polygons("148/41") -NoRCX_m2m4 = source.polygons("148/42") -NoRCX_m2m5 = source.polygons("148/43") -NoRCX_m2tm1 = source.polygons("148/44") -NoRCX_m2tm2 = source.polygons("148/45") -NoRCX_m3m4 = source.polygons("148/46") -NoRCX_m3m5 = source.polygons("148/47") -NoRCX_m3tm1 = source.polygons("148/48") -NoRCX_m3tm2 = source.polygons("148/49") -NoRCX_m4m5 = source.polygons("148/50") -NoRCX_m4tm1 = source.polygons("148/51") -NoRCX_m4tm2 = source.polygons("148/52") -NoRCX_m5tm1 = source.polygons("148/53") -NoRCX_m5tm2 = source.polygons("148/54") -NoRCX_tm1tm2 = source.polygons("148/55") -NoRCX_m1sub = source.polygons("148/123") -NoRCX_m2sub = source.polygons("148/124") -NoRCX_m3sub = source.polygons("148/125") -NoRCX_m4sub = source.polygons("148/126") -NoRCX_m5sub = source.polygons("148/127") -NoRCX_tm1sub = source.polygons("148/0-255") -NoRCX_tm2sub = source.polygons("148/0-255") -SNSBotVia = source.polygons("149/0") -SNSTopVia = source.polygons("151/0") -DeepVia = source.polygons("152/0") -FGEtch = source.polygons("153/0") -CtrGat = source.polygons("154/0") -FGImp = source.polygons("155/0") -EmWiHV = source.polygons("156/0") -LBE = source.polygons("157/0") -AlCuStop = source.polygons("159/0") -NoMetFiller = source.polygons("160/0") -prBoundary = source.polygons("235/0") -Exchange0 = source.polygons("190/0") -Exchange0_pin = source.polygons("190/2") -Exchange0_text = source.labels("190/25") -Exchange1 = source.polygons("191/0") -Exchange1_pin = source.polygons("191/2") -Exchange1_text = source.labels("191/25") -Exchange2 = source.polygons("192/0") -Exchange2_pin = source.polygons("192/2") -Exchange2_text = source.labels("192/25") -Exchange3 = source.polygons("193/0") -Exchange3_pin = source.polygons("193/2") -Exchange3_text = source.labels("193/25") -Exchange4 = source.polygons("194/0") -Exchange4_pin = source.polygons("194/2") -Exchange4_text = source.labels("194/25") -isoNWell = source.polygons("257/0") -PEmWind = source.polygons("11/0") -PEmPoly = source.polygons("53/0") -LDMOS = source.polygons("57/0") -PBiWind = source.polygons("58/0") -Flash = source.polygons("71/0") -Activ_Act_a = Activ.ext_width(0.15.um) -Activ_Act_d = Activ.ext_with_area([["<", 0.122.um2]]) -nmosi_relevant_activ = Activ.ext_or(Activ_mask) -Act_density = Activ.ext_or(Activ_filler) -GP_or_Act = Activ.ext_or(GatPoly) -Gate = Activ.ext_and(GatPoly) -Act_connect = Activ.ext_not(GatPoly) -GatPoly_Gat_e = GatPoly.ext_with_area([["<", 0.09.um2]]) -Gat_density = GatPoly.ext_or(GatPoly_filler) -Cont_SQ = Cont.ext_rectangles(true, false, [["==", 0.16.um]], [["==", 0.16.um]], nil) -ContBar = Cont.ext_with_area([[">", (0.16*0.16).um2]]) -Activ_and_nSD_block = Activ.ext_and(nSD_block) -Metal1_Nslit = Metal1.ext_not(Metal1_slit) -selring_pass = Passiv.with_holes -Passiv_Pad_a1 = Passiv.sized(-150.0.um/2.0, acute_limit).sized(150.0.um/2.0, acute_limit) -Metal2_Nslit = Metal2.ext_not(Metal2_slit) -X2 = nSD_block.ext_or(pSD) -pSD_not_nSD = nSD.ext_not(pSD) -subst_tie_hole = (pSD.holes - pSD.with_holes).without_holes -pSD_pSD_a = pSD.ext_width(0.31.um) -pSD_pSD_k = pSD.ext_with_area([["<", 0.25.um2]]) -Act_Nsram = Activ.ext_not(SRAM) -pSD_Nsram = pSD.ext_not(SRAM) -GP_Nsram = GatPoly.ext_not(SRAM) -Cont_Nsram = Cont.ext_not(SRAM) -V1_Nsram = Via1.ext_not(SRAM) -V2_Nsram = Via2.ext_not(SRAM) -Metal3_Nslit = Metal3.ext_not(Metal3_slit) -Act_NWell = Activ.ext_and(NWell) -NWell_Nsram = NWell.ext_not(SRAM) -NWell_NW_a = NWell.ext_width(0.62.um) -NWell_nBuLay = NWell.ext_and(nBuLay) -isoPWell = nBuLay.ext_not(NWell) -nBuLay_block_NBLB_a = nBuLay_block.ext_width(1.5.um) -nBuLay_nBuLay_block_enc_tmp = nBuLay_block.ext_enclosed(nBuLay, 1.0.um, consider_touch_points: false, polygon_output: true) -nBuLay_nBuLay_block_enc_tmp2 = nBuLay_block.ext_overlapping(nBuLay) -MIM_Mim_a = MIM.ext_width(1.14.um, consider_intersecting_edges: false, polygon_output: true) -MIM_Mim_f = MIM.ext_with_area([["<", 1.3.um2]]) -sealring = EdgeSeal.with_holes -Act_EdgeSeal = Activ.ext_and(EdgeSeal) -Act_Not_EdgeSeal = Activ.ext_not(EdgeSeal) -pSD_edgA1_in = pSD.ext_and(EdgeSeal) -Cont_edgC1_in = Cont.ext_and(EdgeSeal) -Via1_edgC1_in = Via1.ext_and(EdgeSeal) -Via1_edgC1_out = Via1.ext_not(EdgeSeal) -Via2_edgC1_in = Via2.ext_and(EdgeSeal) -Via2_edgC1_out = Via2.ext_not(EdgeSeal) -Cont_outside_EdgeSeal = Cont.ext_outside(EdgeSeal) -Metal1_outside_EdgeSeal = Metal1.ext_outside(EdgeSeal) -Metal2_outside_EdgeSeal = Metal2.ext_outside(EdgeSeal) -Metal3_outside_EdgeSeal = Metal3.ext_outside(EdgeSeal) -Passiv_dfpad = Passiv.ext_and(dfpad) -pad = dfpad.not_outside(Passiv) -cupPad_candidat = Passiv.ext_and(dfpad_pillar) -dfpad_all = dfpad.ext_or(dfpad_pillar, dfpad_sbump) -ThickGateOx_TGO_e = ThickGateOx.ext_space(0.86.um, consider_intersecting_edges: false, polygon_output: true) -ThickGateOx_TGO_f = ThickGateOx.ext_width(0.86.um, consider_intersecting_edges: false, polygon_output: true) -X1 = NWell.ext_or(PWell_block) -PWell_block_PWB_a = PWell_block.ext_width(0.62.um) -PWell_block_PWB_b = PWell_block.ext_space(0.62.um, consider_intersecting_edges: false, polygon_output: true) -Activ_outside_PWell_block = Activ.ext_outside(PWell_block) -V3_Nsram = Via3.ext_not(SRAM) -Via3_edgC1_in = Via3.ext_and(EdgeSeal) -Via3_edgC1_out = Via3.ext_not(EdgeSeal) -Metal4_outside_EdgeSeal = Metal4.ext_outside(EdgeSeal) -Metal4_Nslit = Metal4.ext_not(Metal4_slit) -V4_Nsram = Via4.ext_not(SRAM) -Via4_edgC1_in = Via4.ext_and(EdgeSeal) -Via4_edgC1_out = Via4.ext_not(EdgeSeal) -Metal5_outside_EdgeSeal = Metal5.ext_outside(EdgeSeal) -Metal5_Nslit = Metal5.ext_not(Metal5_slit) -Metal5_slit_MIM_Slt_g_M5_sep_tmp1 = Metal5_slit.ext_separation(MIM, 0.6.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -Metal5_slit_MIM_Slt_g_M5_sep_tmp2 = MIM.ext_coincident_edges(Metal5_slit, outside: true, consider_touch_points: true) -Metal5_slit_MIM_Slt_g_M5_sep_tmp5 = Metal5_slit.ext_and(MIM) -scr1 = Recog_esd.ext_interacting_with_text(TEXT, "scr1") -nmoscl_2 = Recog_esd.ext_interacting_with_text(TEXT, "nmoscl_2") -nmoscl_4 = Recog_esd.ext_interacting_with_text(TEXT, "nmoscl_4") -Rhigh_recognition_0 = EXTBlock.ext_and(pSD) -TopVia1_edgC1_in = TopVia1.ext_and(EdgeSeal) -TopVia1_edgC1_out = TopVia1.ext_not(EdgeSeal) -TopMetal1_Nslit = TopMetal1.ext_not(TopMetal1_slit) -TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp1 = TopMetal1_slit.ext_separation(MIM, 0.6.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp2 = MIM.ext_coincident_edges(TopMetal1_slit, outside: true, consider_touch_points: true) -TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp5 = TopMetal1_slit.ext_and(MIM) -GatPoly_res = GatPoly.ext_or(PolyRes) -TopVia1_or_Vmim = TopVia1.ext_or(Vmim) -TopVia2_edgC1_in = TopVia2.ext_and(EdgeSeal) -TopVia2_edgC1_out = TopVia2.ext_not(EdgeSeal) -holes_TopMetal2 = TopMetal2.holes.merge -TopMetal2_outside_EdgeSeal = TopMetal2.ext_outside(EdgeSeal) -TopMetal2_Nslit = TopMetal2.ext_not(TopMetal2_slit) -M1_density = Metal1.ext_or(Metal1_filler).ext_not(Metal1_slit) -M2_density = Metal2.ext_or(Metal2_filler).ext_not(Metal2_slit) -DigiBnd_ring = DigiBnd.sized(0.01.um, acute_limit).ext_not(DigiBnd) -emi2Pin = Metal2_pin.ext_and(TRANS).ext_interacting_with_text(TEXT, "E") -M3_density = Metal3.ext_or(Metal3_filler).ext_not(Metal3_slit) -nBuLayGen_sized = NWell.sized((-(1+1.0/2)).um, acute_limit).sized((1.0/2).um, acute_limit) -inside_chip_0 = EdgeSeal.sized(3.1.um, acute_limit).ext_and(Passiv) -Act_out_ThickGateOx = Activ.ext_not(Activ.ext_interacting(ThickGateOx)) -PWellBlock_relatedNWell_0 = NWell.not_inside(PWell_block).ext_interacting(PWell_block) -M4_density = Metal4.ext_or(Metal4_filler).ext_not(Metal4_slit) -DigiSub_ring = DigiSub.sized(0.01.um, acute_limit).ext_not(DigiBnd) -M5_density = Metal5.ext_or(Metal5_filler).ext_not(Metal5_slit) -SalBlock_not_nSDBlock_not_esd = SalBlock.ext_not(Recog_esd.ext_or(nSD_block)) -TM1_density = TopMetal1.ext_or(TopMetal1_filler).ext_not(TopMetal1_slit) -TM2_density = TopMetal2.ext_or(TopMetal2_filler).ext_not(TopMetal2_slit) -CHIP = -> do - bbox = RBA::DBox::new() - [Activ, Activ_pin, Activ_mask, Activ_filler, Activ_nofill, Activ_OPC, Activ_iOPC, - Activ_noqrc, BiWind, BiWind_OPC, GatPoly, GatPoly_pin, GatPoly_filler, - GatPoly_nofill, GatPoly_OPC, GatPoly_iOPC, GatPoly_noqrc, Cont, Cont_OPC, - nSD, nSD_block, Metal1, Metal1_pin, Metal1_mask, Metal1_filler, - Metal1_nofill, Metal1_slit, Metal1_text, Metal1_OPC, Metal1_noqrc, - Metal1_res, Metal1_iprobe, Metal1_diffprb, Passiv, Passiv_pin, Passiv_sbump, - Passiv_pillar, Passiv_pdl, Metal2, Metal2_pin, Metal2_mask, Metal2_filler, - Metal2_nofill, Metal2_slit, Metal2_text, Metal2_OPC, Metal2_noqrc, - Metal2_res, Metal2_iprobe, Metal2_diffprb, BasPoly, BasPoly_pin, pSD, NLDB, - DigiBnd, Via1, BackMetal1, BackMetal1_pin, BackMetal1_mask, - BackMetal1_filler, BackMetal1_nofill, BackMetal1_slit, BackMetal1_text, - BackMetal1_OPC, BackMetal1_noqrc, BackMetal1_res, BackMetal1_iprobe, - BackMetal1_diffprb, BackPassiv, RES, SRAM, TRANS, IND, IND_pin, IND_text, - SalBlock, Via2, Metal3, Metal3_pin, Metal3_mask, Metal3_filler, - Metal3_nofill, Metal3_slit, Metal3_text, Metal3_OPC, Metal3_noqrc, - Metal3_res, Metal3_iprobe, Metal3_diffprb, NWell, NWell_pin, nBuLay, - nBuLay_pin, nBuLay_block, EmWind, EmWind_OPC, DeepCo, MIM, EdgeSeal, - Substrate, Substrate_text, dfpad, dfpad_pillar, dfpad_sbump, ThickGateOx, - PLDB, PWell, PWell_pin, PWell_block, IC, Via3, Metal4, Metal4_pin, - Metal4_mask, Metal4_filler, Metal4_nofill, Metal4_slit, Metal4_text, - Metal4_OPC, Metal4_noqrc, Metal4_res, Metal4_iprobe, Metal4_diffprb, - HeatTrans, HeatRes, FBE, EmPoly, DigiSub, NoDRC, TEXT, Via4, Metal5, - Metal5_pin, Metal5_mask, Metal5_filler, Metal5_nofill, Metal5_slit, - Metal5_text, Metal5_OPC, Metal5_noqrc, Metal5_res, Metal5_iprobe, - Metal5_diffprb, RadHard, MemCap, Varicap, IntBondVia, IntBondMet, DevBondVia, - DevBondMet, DevTrench, Redist, GraphBot, GraphTop, AntVia1, AntMetal2, - GraphCont, SiWG, SiWG_filler, SiWG_nofill, SiGrating, SiNGrating, GraphPas, - EmWind3, EmWiHV3, RedBuLay, SMOS, GraphPad, Polimide, Polimide_pin, Recog, - Recog_pin, Recog_esd, Recog_diode, Recog_tsv, Recog_iprobe, Recog_diffprb, - Recog_pillar, Recog_sbump, Recog_otp, Recog_pdiode, Recog_mom, Recog_pcm, - ColOpen, GraphMetal1, GraphMetal1_filler, GraphMetal1_nofill, - GraphMetal1_slit, GraphMetal1_OPC, GraphMet1L, GraphMet1L_filler, - GraphMet1L_nofill, GraphMet1L_slit, GraphMet1L_OPC, EXTBlock, NLDD, PLDD, - NExt, PExt, NExtHV, PExtHV, GraphGate, SiNWG, SiNWG_filler, SiNWG_nofill, - MEMPAD, TopVia1, TopMetal1, TopMetal1_pin, TopMetal1_mask, TopMetal1_filler, - TopMetal1_nofill, TopMetal1_slit, TopMetal1_text, TopMetal1_noqrc, - TopMetal1_res, TopMetal1_iprobe, TopMetal1_diffprb, INLDPWL, PolyRes, - PolyRes_pin, Vmim, nBuLayCut, AntMetal1, TopVia2, TopMetal2, TopMetal2_pin, - TopMetal2_mask, TopMetal2_filler, TopMetal2_nofill, TopMetal2_slit, - TopMetal2_text, TopMetal2_noqrc, TopMetal2_res, TopMetal2_iprobe, - TopMetal2_diffprb, SNSRing, Sensor, SNSArms, SNSCMOSVia, ColWind, FLM, - HafniumOx, MEMVia, ThinFilmRes, RFMEM, NoRCX, NoRCX_m2m3, NoRCX_m2m4, - NoRCX_m2m5, NoRCX_m2tm1, NoRCX_m2tm2, NoRCX_m3m4, NoRCX_m3m5, NoRCX_m3tm1, - NoRCX_m3tm2, NoRCX_m4m5, NoRCX_m4tm1, NoRCX_m4tm2, NoRCX_m5tm1, NoRCX_m5tm2, - NoRCX_tm1tm2, NoRCX_m1sub, NoRCX_m2sub, NoRCX_m3sub, NoRCX_m4sub, - NoRCX_m5sub, NoRCX_tm1sub, NoRCX_tm2sub, SNSBotVia, SNSTopVia, DeepVia, - FGEtch, CtrGat, FGImp, EmWiHV, LBE, AlCuStop, NoMetFiller, prBoundary, - Exchange0, Exchange0_pin, Exchange0_text, Exchange1, Exchange1_pin, - Exchange1_text, Exchange2, Exchange2_pin, Exchange2_text, Exchange3, - Exchange3_pin, Exchange3_text, Exchange4, Exchange4_pin, Exchange4_text, - isoNWell].each { |layer| - bbox += layer.bbox - } - DRC::DRCLayer::new(self, RBA::Region::new(bbox.to_itype(dbu))) -end.() -GP_mosHV = Gate.not_outside(ThickGateOx) -GP_out_ThickGateOx = Gate.ext_outside(ThickGateOx) -size_Cont = Cont.ext_enlarge_inside(Act_connect, 6.um, 0.21.um) -Cont_GP = Cont_SQ.ext_and(GatPoly) -Cont_Act = Cont_SQ.ext_and(Activ) -Cont_not_M1 = Cont_SQ.ext_not(Metal1) -Cont_Act_GP = Cont_SQ.ext_and(Gate) -CntB_a1_error = ContBar.ext_with_area([["<", (0.16*0.34).um2]]) -ContBar_GP = ContBar.ext_and(GatPoly) -ContBar_Act = ContBar.ext_and(Activ) -ContBar_not_M1 = ContBar.ext_not(Metal1) -ContBar_Act_GP = ContBar.ext_and(Gate) -ContBar_outside_TRANS = ContBar.ext_outside(TRANS) -dschottky_1 = Activ_and_nSD_block.ext_and(nBuLay) -M1_Nsram = Metal1_Nslit.ext_not(SRAM) -Metal1_edgA1_in = Metal1_Nslit.ext_and(EdgeSeal) -M2_Nsram = Metal2_Nslit.ext_not(SRAM) -Metal2_edgA1_in = Metal2_Nslit.ext_and(EdgeSeal) -dpin_0 = BasPoly.ext_and(Activ).ext_and(BiWind).ext_and(nSD_block) -nSD_not_pSD = pSD_not_nSD.dup -subst_tie_hole_w_npn = subst_tie_hole.ext_interacting_with_text(TEXT, "npn*") -pSDL_enc_area = subst_tie_hole.ext_not(pSD) -Act_Nsram_or_Activ_mask = Act_Nsram.ext_or(Activ_mask) -pSDHV_Nsram = pSD_Nsram.inside(ThickGateOx) -GP_Nsram_Gat_a = GP_Nsram.ext_width(0.13.um, consider_intersecting_edges: false, polygon_output: true) -GP_Nsram_Gat_b = GP_Nsram.ext_space(0.18.um, consider_intersecting_edges: false, polygon_output: true) -V1_Nsram_outside_EdgeSeal = V1_Nsram.ext_outside(EdgeSeal) -npnMPA_0 = nBuLay.ext_and(Activ.ext_and(SalBlock.ext_and(nSD_block))) -V2_Nsram_outside_EdgeSeal = V2_Nsram.ext_outside(EdgeSeal) -M3_Nsram = Metal3_Nslit.ext_not(SRAM) -Metal3_edgA1_in = Metal3_Nslit.ext_and(EdgeSeal) -nBuLay_nBuLay_block_enc_tmp3 = nBuLay_nBuLay_block_enc_tmp + nBuLay_nBuLay_block_enc_tmp2 -ring_passiv = selring_pass.ext_outside(sealring) -Act_EdgeSeal_not_HRACT = Act_EdgeSeal.ext_not(Recog) -Activ_edgA1_in = Act_EdgeSeal.dup -Act_EdgeSeal_Cont_edgC1_in_enc_tmp = Cont_edgC1_in.ext_enclosed(Act_EdgeSeal, 1.3.um, consider_touch_points: false, polygon_output: true) -Act_EdgeSeal_Cont_edgC1_in_enc_tmp2 = Cont_edgC1_in.ext_overlapping(Act_EdgeSeal) -Act_EdgeSeal_Via1_edgC1_in_enc_tmp = Via1_edgC1_in.ext_enclosed(Act_EdgeSeal, 1.3.um, consider_touch_points: false, polygon_output: true) -Act_EdgeSeal_Via1_edgC1_in_enc_tmp2 = Via1_edgC1_in.ext_overlapping(Act_EdgeSeal) -Act_EdgeSeal_Via2_edgC1_in_enc_tmp = Via2_edgC1_in.ext_enclosed(Act_EdgeSeal, 1.3.um, consider_touch_points: false, polygon_output: true) -Act_EdgeSeal_Via2_edgC1_in_enc_tmp2 = Via2_edgC1_in.ext_overlapping(Act_EdgeSeal) -Metal1_slit_not_pad = Metal1_slit.ext_not(pad) -Metal2_slit_not_pad = Metal2_slit.ext_not(pad) -Metal3_slit_not_pad = Metal3_slit.ext_not(pad) -Metal4_slit_not_pad = Metal4_slit.ext_not(pad) -Metal5_slit_not_pad = Metal5_slit.ext_not(pad) -TopMetal1_slit_not_pad = TopMetal1_slit.ext_not(pad) -TopMetal2_slit_not_pad = TopMetal2_slit.ext_not(pad) -Recog_or_dfpad_all = Recog.ext_or(dfpad_all) -Recog_or_MIM_or_dfpad_all = MIM.ext_or(Recog, dfpad_all) -Iso_PWell_Act = Activ.ext_and(nBuLay).ext_not(X1) -lPWB_e_1 = Activ_outside_PWell_block.ext_not(pSD) -lPWB_f_1 = Activ_outside_PWell_block.ext_and(pSD) -V3_Nsram_outside_EdgeSeal = V3_Nsram.ext_outside(EdgeSeal) -Act_EdgeSeal_Via3_edgC1_in_enc_tmp = Via3_edgC1_in.ext_enclosed(Act_EdgeSeal, 1.3.um, consider_touch_points: false, polygon_output: true) -Act_EdgeSeal_Via3_edgC1_in_enc_tmp2 = Via3_edgC1_in.ext_overlapping(Act_EdgeSeal) -M4_Nsram = Metal4_Nslit.ext_not(SRAM) -Metal4_edgA1_in = Metal4_Nslit.ext_and(EdgeSeal) -V4_Nsram_outside_EdgeSeal = V4_Nsram.ext_outside(EdgeSeal) -Act_EdgeSeal_Via4_edgC1_in_enc_tmp = Via4_edgC1_in.ext_enclosed(Act_EdgeSeal, 1.3.um, consider_touch_points: false, polygon_output: true) -Act_EdgeSeal_Via4_edgC1_in_enc_tmp2 = Via4_edgC1_in.ext_overlapping(Act_EdgeSeal) -cmim_a = MIM.not_outside(Metal5).not_outside(TopMetal1).not_outside(Vmim) -M5_Nsram = Metal5_Nslit.ext_not(SRAM) -belowTopMetaln_dfpad = Metal5_Nslit.ext_and(dfpad) -Metal5_edgA1_in = Metal5_Nslit.ext_and(EdgeSeal) -Metal5_slit_MIM_Slt_g_M5_sep_tmp3 = Metal5_slit.ext_with_coincident_edges(Metal5_slit_MIM_Slt_g_M5_sep_tmp2) -nmoscl = nmoscl_2.ext_or(nmoscl_4) -Rhigh_recognition_1 = Rhigh_recognition_0.ext_and(nSD) -Act_EdgeSeal_TopVia1_edgC1_in_enc_tmp = TopVia1_edgC1_in.ext_enclosed(Act_EdgeSeal, 1.3.um, consider_touch_points: false, polygon_output: true) -Act_EdgeSeal_TopVia1_edgC1_in_enc_tmp2 = TopVia1_edgC1_in.ext_overlapping(Act_EdgeSeal) -TopMetal1_edgA1_in = TopMetal1_Nslit.ext_and(EdgeSeal) -TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp3 = TopMetal1_slit.ext_with_coincident_edges(TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp2) -temp_layer_1 = MIM.ext_covering(TopVia1_or_Vmim) -Act_EdgeSeal_TopVia2_edgC1_in_enc_tmp = TopVia2_edgC1_in.ext_enclosed(Act_EdgeSeal, 1.3.um, consider_touch_points: false, polygon_output: true) -Act_EdgeSeal_TopVia2_edgC1_in_enc_tmp2 = TopVia2_edgC1_in.ext_overlapping(Act_EdgeSeal) -temp_layer_6 = TopMetal2.ext_or(holes_TopMetal2) -TopMetal2_edgA1_in = TopMetal2_Nslit.ext_and(EdgeSeal) -Cont_not_Act_GP = Cont_SQ.ext_not(GP_or_Act).ext_outside(TRANS) -ContBar_not_Act_GP = ContBar.ext_not(GP_or_Act).ext_outside(TRANS) -nSD_drv = nSD.ext_or(Activ.ext_not(X2)) -X2_Extent = X2.ext_extents.sized(0.001.um, acute_limit) -transG2 = TRANS.ext_interacting_with_text(TEXT, "npn13G2").ext_covering(emi2Pin) -transG2C = TRANS.ext_interacting_with_text(TEXT, "npn13G2C").ext_covering(emi2Pin) -transG2L = TRANS.ext_interacting_with_text(TEXT, "npn13G2L").ext_covering(emi2Pin) -transG2V = TRANS.ext_interacting_with_text(TEXT, "npn13G2V").ext_covering(emi2Pin) -nBuLayGen = nBuLayGen_sized.ext_not(nBuLay_block) -inside_chip_holes = inside_chip_0.holes.merge -ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp1 = ThickGateOx.ext_separation(Act_out_ThickGateOx, 0.27.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp2 = Act_out_ThickGateOx.ext_coincident_edges(ThickGateOx, outside: true, consider_touch_points: true) -ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp5 = ThickGateOx.ext_and(Act_out_ThickGateOx) -PWellBlock_relatedNWell = PWellBlock_relatedNWell_0.ext_or(NWell.inside(PWell_block)) -X1_Extent = X1.ext_extents.sized(0.001.um, acute_limit) -Rppd_0 = GatPoly_res.ext_and(pSD).ext_and(SalBlock_not_nSDBlock_not_esd) -SUB = CHIP.sized(1.um, acute_limit) -GP_mosHV_Gat_b1 = GP_mosHV.ext_space(0.25.um, consider_intersecting_edges: false, polygon_output: true) -ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp1 = ThickGateOx.ext_separation(GP_out_ThickGateOx, 0.34.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp2 = GP_out_ThickGateOx.ext_coincident_edges(ThickGateOx, outside: true, consider_touch_points: true) -ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp5 = ThickGateOx.ext_and(GP_out_ThickGateOx) -dschottky_2 = dschottky_1.sized(1.12.um, acute_limit) -dpin_1 = dpin_0.sized(1.12.um, acute_limit) -pSD_not_nSD_or_nSD_not_pSD = nSD_not_pSD.ext_or(pSD_not_nSD) -subst_tie_trans = TRANS.inside(subst_tie_hole_w_npn) -pSDL_enc_area_pSD_l = pSDL_enc_area.ext_with_area([["<", 0.25.um2]]) -DigiBnd_hole = DigiBnd.ext_or(DigiBnd_ring.holes.merge) -npnMPA = npnMPA_0.ext_interacting_with_text(TEXT, "npnMPA") -nBuLay_nBuLay_block_enc_tmp6 = nBuLay_nBuLay_block_enc_tmp3.dup -ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp1 = ring_passiv.ext_separation(pSD_edgA1_in, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp2 = pSD_edgA1_in.ext_coincident_edges(ring_passiv, outside: true, consider_touch_points: true) -ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp5 = ring_passiv.ext_and(pSD_edgA1_in) -ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp1 = ring_passiv.ext_separation(Metal1_edgA1_in, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp2 = Metal1_edgA1_in.ext_coincident_edges(ring_passiv, outside: true, consider_touch_points: true) -ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp5 = ring_passiv.ext_and(Metal1_edgA1_in) -ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp1 = ring_passiv.ext_separation(Metal2_edgA1_in, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp2 = Metal2_edgA1_in.ext_coincident_edges(ring_passiv, outside: true, consider_touch_points: true) -ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp5 = ring_passiv.ext_and(Metal2_edgA1_in) -ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp1 = ring_passiv.ext_separation(Metal3_edgA1_in, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp2 = Metal3_edgA1_in.ext_coincident_edges(ring_passiv, outside: true, consider_touch_points: true) -ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp5 = ring_passiv.ext_and(Metal3_edgA1_in) -ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp1 = ring_passiv.ext_separation(Activ_edgA1_in, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp2 = Activ_edgA1_in.ext_coincident_edges(ring_passiv, outside: true, consider_touch_points: true) -ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp5 = ring_passiv.ext_and(Activ_edgA1_in) -Act_EdgeSeal_Cont_edgC1_in_enc_tmp3 = Act_EdgeSeal_Cont_edgC1_in_enc_tmp + Act_EdgeSeal_Cont_edgC1_in_enc_tmp2 -Act_EdgeSeal_Via1_edgC1_in_enc_tmp3 = Act_EdgeSeal_Via1_edgC1_in_enc_tmp + Act_EdgeSeal_Via1_edgC1_in_enc_tmp2 -Act_EdgeSeal_Via2_edgC1_in_enc_tmp3 = Act_EdgeSeal_Via2_edgC1_in_enc_tmp + Act_EdgeSeal_Via2_edgC1_in_enc_tmp2 -sltc_M1 = Metal1_Nslit.ext_not(Recog_or_dfpad_all) -sltc_M2 = Metal2_Nslit.ext_not(Recog_or_dfpad_all) -sltc_M3 = Metal3_Nslit.ext_not(Recog_or_dfpad_all) -sltc_M4 = Metal4_Nslit.ext_not(Recog_or_dfpad_all) -sltc_TM2 = TopMetal2_Nslit.ext_not(Recog_or_dfpad_all) -sltc_M5 = Metal5_Nslit.ext_not(Recog_or_MIM_or_dfpad_all) -sltc_TM1 = TopMetal1_Nslit.ext_not(Recog_or_MIM_or_dfpad_all) -nSDBlock_Iso_PWell_Act = nSD_block.not_outside(Iso_PWell_Act) -SalBlock_Iso_PWell_Act = SalBlock.not_outside(Iso_PWell_Act) -Act_EdgeSeal_Via3_edgC1_in_enc_tmp3 = Act_EdgeSeal_Via3_edgC1_in_enc_tmp + Act_EdgeSeal_Via3_edgC1_in_enc_tmp2 -ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp1 = ring_passiv.ext_separation(Metal4_edgA1_in, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp2 = Metal4_edgA1_in.ext_coincident_edges(ring_passiv, outside: true, consider_touch_points: true) -ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp5 = ring_passiv.ext_and(Metal4_edgA1_in) -Act_EdgeSeal_Via4_edgC1_in_enc_tmp3 = Act_EdgeSeal_Via4_edgC1_in_enc_tmp + Act_EdgeSeal_Via4_edgC1_in_enc_tmp2 -rfcmim_a = cmim_a.not_outside(PWell_block.ext_interacting_with_text(TEXT, "rfcmim")) -ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp1 = ring_passiv.ext_separation(Metal5_edgA1_in, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp2 = Metal5_edgA1_in.ext_coincident_edges(ring_passiv, outside: true, consider_touch_points: true) -ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp5 = ring_passiv.ext_and(Metal5_edgA1_in) -Metal5_slit_MIM_Slt_g_M5_sep_tmp4 = Metal5_slit_MIM_Slt_g_M5_sep_tmp1 + Metal5_slit_MIM_Slt_g_M5_sep_tmp3 -Rhigh_recognition = Rhigh_recognition_1.ext_covering(GatPoly) -Act_EdgeSeal_TopVia1_edgC1_in_enc_tmp3 = Act_EdgeSeal_TopVia1_edgC1_in_enc_tmp + Act_EdgeSeal_TopVia1_edgC1_in_enc_tmp2 -ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp1 = ring_passiv.ext_separation(TopMetal1_edgA1_in, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp2 = TopMetal1_edgA1_in.ext_coincident_edges(ring_passiv, outside: true, consider_touch_points: true) -ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp5 = ring_passiv.ext_and(TopMetal1_edgA1_in) -TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp4 = TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp1 + TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp3 -Rsil_all = GatPoly_res.ext_and(RES).ext_and(EXTBlock).ext_interacting(SalBlock, inverted: true) -Act_EdgeSeal_TopVia2_edgC1_in_enc_tmp3 = Act_EdgeSeal_TopVia2_edgC1_in_enc_tmp + Act_EdgeSeal_TopVia2_edgC1_in_enc_tmp2 -ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp1 = ring_passiv.ext_separation(TopMetal2_edgA1_in, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp2 = TopMetal2_edgA1_in.ext_coincident_edges(ring_passiv, outside: true, consider_touch_points: true) -ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp5 = ring_passiv.ext_and(TopMetal2_edgA1_in) -NAct = Activ.ext_and(nSD_drv) -pSD_nSD = pSD.ext_and(nSD_drv) -Y2 = X2_Extent.ext_not(X2) -subst_tie_npn = pSD.ext_touching(subst_tie_hole_w_npn).ext_touching(TRANS) -emit_npn13G2 = EmWind.inside(transG2) -emit_npn13G2L = EmWind.inside(transG2L) -trans_bip = transG2.ext_or(transG2C, transG2L, transG2V) -emit_npn13G2V = EmWind.inside(transG2V) -nBuLayGen_nBuLay = nBuLay.ext_or(nBuLayGen) -schottky_nbl_rec = isoPWell.not_outside(SalBlock).not_outside(nSD_block).not_outside(Recog_diode).not_outside(ThickGateOx) -ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp3 = ThickGateOx.ext_with_coincident_edges(ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp2) -PWellBlock_unrelatedNWell = NWell.ext_not(PWellBlock_relatedNWell) -Y1 = X1_Extent.ext_not(X1) -Bulk = SUB.ext_not(DigiSub_ring) -ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp3 = ThickGateOx.ext_with_coincident_edges(ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp2) -dschottky_3 = dschottky_2.ext_and(PWell_block) -dpin = dpin_1.ext_and(PWell_block) -Rppd_all = Rppd_0.ext_interacting(Activ.ext_or(nSD_drv), inverted: true) -nBuLay_nBuLay_block_enc = nBuLay_nBuLay_block_enc_tmp6.dup -ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp3 = ring_passiv.ext_with_coincident_edges(ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp2) -ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp3 = ring_passiv.ext_with_coincident_edges(ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp2) -ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp3 = ring_passiv.ext_with_coincident_edges(ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp2) -ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp3 = ring_passiv.ext_with_coincident_edges(ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp2) -ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp3 = ring_passiv.ext_with_coincident_edges(ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp2) -Act_EdgeSeal_Cont_edgC1_in_enc_tmp6 = Act_EdgeSeal_Cont_edgC1_in_enc_tmp3.dup -Act_EdgeSeal_Via1_edgC1_in_enc_tmp6 = Act_EdgeSeal_Via1_edgC1_in_enc_tmp3.dup -Act_EdgeSeal_Via2_edgC1_in_enc_tmp6 = Act_EdgeSeal_Via2_edgC1_in_enc_tmp3.dup -Act_EdgeSeal_Via3_edgC1_in_enc_tmp6 = Act_EdgeSeal_Via3_edgC1_in_enc_tmp3.dup -ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp3 = ring_passiv.ext_with_coincident_edges(ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp2) -Act_EdgeSeal_Via4_edgC1_in_enc_tmp6 = Act_EdgeSeal_Via4_edgC1_in_enc_tmp3.dup -ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp3 = ring_passiv.ext_with_coincident_edges(ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp2) -Metal5_slit_MIM_Slt_g_M5_sep_tmp6 = Metal5_slit_MIM_Slt_g_M5_sep_tmp4 + Metal5_slit_MIM_Slt_g_M5_sep_tmp5 -Rhigh_identical_nsd_psd_edge = pSD_not_nSD_or_nSD_not_pSD.ext_coincident_part(Rhigh_recognition, outside: true) -Act_EdgeSeal_TopVia1_edgC1_in_enc_tmp6 = Act_EdgeSeal_TopVia1_edgC1_in_enc_tmp3.dup -ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp3 = ring_passiv.ext_with_coincident_edges(ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp2) -TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp6 = TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp4 + TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp5 -rsil_gatpoly = GatPoly_res.not_outside(Rsil_all) -Rsil_all_not_interact_NWell = Rsil_all.ext_interacting(NWell, inverted: true) -Act_EdgeSeal_TopVia2_edgC1_in_enc_tmp6 = Act_EdgeSeal_TopVia2_edgC1_in_enc_tmp3.dup -ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp3 = ring_passiv.ext_with_coincident_edges(ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp2) -NGate = Gate.not_outside(NAct) -PAct = Activ.ext_not(NAct) -PAct_connect = Act_connect.ext_not(NAct) -NActLV = NAct.ext_not(ThickGateOx) -NAct_NWell = NAct.ext_and(X1) -sal_nActiv = NAct.ext_not(SalBlock) -Cont_NAct = Cont_SQ.ext_and(NAct) -ContBar_NAct = ContBar.ext_and(NAct) -Cont_not_outside_NAct = Cont.not_outside(NAct) -nBuLayGen_nBuLay_NBL_a = nBuLayGen_nBuLay.ext_width(1.0.um) -inside_chip_1 = inside_chip_0.ext_or(inside_chip_holes).ext_interacting(inside_chip_holes).sized(-4.2.um, acute_limit) -ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp4 = ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp1 + ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp3 -rfcmim = PWell_block.not_outside(rfcmim_a).sized(0.65.um, acute_limit) -PWell_drv = Bulk.ext_not(X1) -ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp4 = ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp1 + ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp3 -dschottky = dschottky_3.ext_not(dpin) -GP_Rppd_extended = GatPoly_res.ext_covering(Rppd_all) -SalBlock_Rppd = SalBlock.ext_and(Rppd_all) -Rppd_all_enclosure_pSD = Rppd_all.ext_enclosed(pSD, 0.18.um) -Rhigh_a = GatPoly_res.ext_and(pSD_nSD).ext_and(SalBlock_not_nSDBlock_not_esd) -schottky_nbl1_nw = NWell.ext_interacting(NWell.holes.merge.ext_covering(schottky_nbl_rec)) -schottky_nw1_rect = NWell.not_outside(nSD_block).ext_interacting(schottky_nbl_rec, inverted: true).ext_and(Recog_diode) -ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp4 = ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp1 + ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp3 -ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp4 = ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp1 + ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp3 -ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp4 = ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp1 + ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp3 -ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp4 = ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp1 + ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp3 -ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp4 = ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp1 + ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp3 -Act_EdgeSeal_Cont_edgC1_in_enc = Act_EdgeSeal_Cont_edgC1_in_enc_tmp6.dup -Act_EdgeSeal_Via1_edgC1_in_enc = Act_EdgeSeal_Via1_edgC1_in_enc_tmp6.dup -Act_EdgeSeal_Via2_edgC1_in_enc = Act_EdgeSeal_Via2_edgC1_in_enc_tmp6.dup -Act_EdgeSeal_Via3_edgC1_in_enc = Act_EdgeSeal_Via3_edgC1_in_enc_tmp6.dup -ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp4 = ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp1 + ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp3 -Act_EdgeSeal_Via4_edgC1_in_enc = Act_EdgeSeal_Via4_edgC1_in_enc_tmp6.dup -ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp4 = ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp1 + ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp3 -Metal5_slit_MIM_Slt_g_M5_sep_tmp9 = Metal5_slit_MIM_Slt_g_M5_sep_tmp6.dup -Rhigh_identical_nsd_psd = pSD_not_nSD_or_nSD_not_pSD.ext_with_coincident_edges(Rhigh_identical_nsd_psd_edge) -Act_EdgeSeal_TopVia1_edgC1_in_enc = Act_EdgeSeal_TopVia1_edgC1_in_enc_tmp6.dup -ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp4 = ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp1 + ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp3 -TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp9 = TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp6.dup -Rsil = Rsil_all_not_interact_NWell.ext_interacting(nBuLay, inverted: true) -Act_EdgeSeal_TopVia2_edgC1_in_enc = Act_EdgeSeal_TopVia2_edgC1_in_enc_tmp6.dup -ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp4 = ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp1 + ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp3 -PGate = Gate.ext_outside(NGate) -NAct_connect = Act_connect.ext_not(PAct) -PActLV = PAct.ext_not(ThickGateOx) -PAct_NWell = PAct.ext_and(X1) -Cont_PAct = Cont_SQ.ext_and(PAct) -ContBar_PAct = ContBar.ext_and(PAct) -npnPActRing = PAct.ext_interacting(TRANS.ext_interacting_with_text(TEXT, "npn13*").sized(0.2.um, acute_limit)) -Cont_not_outside_PAct = Cont.not_outside(PAct) -NActHV = NAct.ext_not(NActLV) -NAct_NWellLV = NAct_NWell.ext_not(ThickGateOx) -NAct_PWell = NAct.ext_not(NAct_NWell) -WellContDev = NAct_NWell.ext_interacting_with_text(TEXT, "well") -NAct_NWell_not_Gate = NAct_NWell.ext_not(Gate) -sal_nactive = sal_nActiv.dup -Rppd_Cont = EXTBlock.ext_covering(Rppd_all).ext_and(Cont) -n_tie = NWell.ext_and(Activ.ext_and(Y2)).ext_not(SalBlock) -ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp6 = ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp4 + ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp5 -p_tie = pSD.ext_and(Activ.ext_and(Y1)).ext_not(SalBlock) -Act_PWell = Activ.ext_and(PWell_drv) -ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp6 = ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp4 + ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp5 -SVaricap_gate_0 = NGate.not_outside(NWell).not_outside(nBuLay) -GP_Rhigh_extended = GatPoly_res.ext_covering(Rhigh_a) -SalBlock_Rhigh = SalBlock.ext_and(Rhigh_a) -schottky_nbl1 = schottky_nbl1_nw.sized(1.36.um, acute_limit) -temp = Bulk.ext_not(Bulk.ext_interacting(inside_chip_1)) -ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp6 = ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp4 + ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp5 -ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp6 = ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp4 + ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp5 -ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp6 = ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp4 + ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp5 -ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp6 = ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp4 + ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp5 -ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp6 = ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp4 + ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp5 -ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp6 = ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp4 + ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp5 -ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp6 = ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp4 + ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp5 -Metal5_slit_MIM_Slt_g_M5_sep_tmp11 = Metal5_slit_MIM_Slt_g_M5_sep_tmp9.dup -ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp6 = ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp4 + ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp5 -TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp11 = TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp9.dup -GP_Rsil_extended = GatPoly_res.ext_covering(Rsil) -ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp6 = ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp4 + ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp5 -X = nBuLayGen_nBuLay.ext_separation(NAct_connect, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) -PActHV = PAct.ext_not(PActLV) -PAct_PWell = PAct.ext_not(PAct_NWell) -Abut_NWell_Tie_Edge = NAct_NWell.ext_coincident_part(PAct_NWell, outside: true) -MVaricap = PWell_block.ext_and(NWell.sized(1.0.um, acute_limit)).not_outside(GatPoly).not_outside(nBuLay).not_outside(PAct).not_outside(NAct).ext_interacting_with_text(TEXT, "MVaricap") -NActHV_digi = NActHV.not_outside(DigiBnd_hole) -NAct_NWellHV = NAct_NWell.ext_not(NAct_NWellLV) -ntaparea = sal_nactive.ext_and(NWell) -Rhigh_Cont = EXTBlock.ext_covering(Rhigh_a).ext_and(Cont) -hard_n_tie = n_tie.ext_covering(Cont) -schottky_nw1_sized = schottky_nw1_rect.sized(1.36.um, acute_limit).ext_and(ThickGateOx) -ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp9 = ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp6.dup -ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp9 = ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp6.dup -SVaricap_poly = GatPoly.not_outside(SVaricap_gate_0) -NWell_Tie = NAct_NWell.ext_not(WellContDev.ext_or(SalBlock.ext_or(TRANS))) -schottky_pwb = schottky_nbl1.ext_and(PWell_block) -schottky_nSDBlock = schottky_nbl1.ext_and(nSD_block) -schottky_salblock = schottky_nbl1.ext_and(SalBlock) -schottky_contbar = schottky_nbl1.ext_and(ContBar) -scr1_or_schottky_nbl1 = schottky_nbl1.ext_or(scr1) -inside_chip_2 = inside_chip_1.ext_or(temp) -ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp9 = ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp6.dup -ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp9 = ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp6.dup -ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp9 = ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp6.dup -ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp9 = ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp6.dup -ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp9 = ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp6.dup -ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp9 = ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp6.dup -ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp9 = ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp6.dup -Metal5_slit_MIM_Slt_g_M5_sep = Metal5_slit_MIM_Slt_g_M5_sep_tmp11.dup -ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp9 = ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp6.dup -TopMetal1_slit_MIM_Slt_g_TM1_sep = TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp11.dup -GP_Rsil_extended_external_pSD = GP_Rsil_extended.ext_separation(pSD, 0.18.um) -ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp9 = ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp6.dup -SVaricap_text = Activ.not_outside(SVaricap_gate_0).ext_interacting_with_text(TEXT, "SVaricap") -nBuLay_e = X.not_outside(PWell_drv) -PActHV_digi = PActHV.not_outside(DigiBnd_hole) -PAct_PWellLV = PAct_PWell.ext_not(ThickGateOx) -cmim_tie = PAct_PWell.not_outside(rfcmim) -Abut_PWell_Tie_Edge = PAct_PWell.ext_coincident_part(NAct_PWell, outside: true) -BJT_ring_a = PAct_PWell.with_holes -PAct_PWell_not_Gate = PAct_PWell.ext_not(Gate) -Abut_NWell_Tie = NAct_NWell.ext_with_coincident_edges(Abut_NWell_Tie_Edge) -NActHV_ana = NActHV.ext_not(NActHV_digi) -NAct_NWellHV_digi = NAct_NWellHV.not_outside(DigiBnd_hole) -soft_p_tie = p_tie.ext_not(hard_n_tie) -soft_n_tie = n_tie.ext_not(hard_n_tie) -schottky_nbl1_b = PAct_connect.not_outside(schottky_nbl1).ext_not(schottky_nbl1) -schottky_nw1 = schottky_nw1_sized.ext_interacting_with_text(TEXT, "schottky_nw1") -ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp11 = ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp9.dup -ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp11 = ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp9.dup -MOSvaricap = MVaricap.ext_or(SVaricap_poly) -SubContDev_basic = PAct_PWell.ext_interacting_with_text(TEXT, "sub!").ext_not(Recog_esd) -NwellRing_innermost = NWell_Tie.holes.merge.outside(NWell_Tie) -ntap = ntaparea.ext_covering(Cont.ext_and(ntaparea)) -PasInsideChip = Passiv.ext_and(inside_chip_2) -ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp11 = ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp9.dup -ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp11 = ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp9.dup -ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp11 = ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp9.dup -ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp11 = ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp9.dup -ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp11 = ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp9.dup -ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp11 = ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp9.dup -ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp11 = ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp9.dup -ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp11 = ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp9.dup -ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp11 = ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp9.dup -SVaricap = NWell.not_outside(SVaricap_text) -PActHV_ana = PActHV.ext_not(PActHV_digi) -PAct_PWellHV = PAct_PWell.ext_not(PAct_PWellLV) -Abut_PWell_Tie = PAct_PWell.ext_with_coincident_edges(Abut_PWell_Tie_Edge) -Abut_NWell_Tie_PAct = PAct.ext_interacting(Abut_NWell_Tie) -NAct_NWellHV_ana = NAct_NWellHV.ext_not(NAct_NWellHV_digi) -all_ptie = Cont_PAct.ext_or(soft_p_tie) -nsdb_exlcDev = dschottky.ext_or(schottky_nbl1, schottky_nw1, trans_bip) -schottky_nbl1_or_schottky_nw1 = schottky_nbl1.ext_or(schottky_nw1) -ThickGateOx_Act_out_ThickGateOx_TGO_b_sep = ThickGateOx_Act_out_ThickGateOx_TGO_b_sep_tmp11.dup -ThickGateOx_GP_out_ThickGateOx_TGO_d_sep = ThickGateOx_GP_out_ThickGateOx_TGO_d_sep_tmp11.dup -SubContDev = SubContDev_basic.ext_interacting(nBuLay, inverted: true) -SubContDev_iso = SubContDev_basic.not_outside(nBuLay) -PGate_inside_NwellRing = PGate.not_outside(NwellRing_innermost) -NwellRing_edge = NWell_Tie.ext_coincident_part(NwellRing_innermost, outside: true) -all_ntie = ntap.ext_or(soft_n_tie) -schottky_nw1_b = PAct_connect.not_outside(schottky_nw1).ext_not(schottky_nw1) -ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep = ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep_tmp11.dup -ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep = ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep_tmp11.dup -ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep = ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep_tmp11.dup -ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep = ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep_tmp11.dup -ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep = ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep_tmp11.dup -ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep = ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep_tmp11.dup -ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep = ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep_tmp11.dup -ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep = ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep_tmp11.dup -ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep = ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep_tmp11.dup -pSD_c_tmp1 = pSD.ext_outside(SVaricap) -pSD_c1_exclusion = EdgeSeal.ext_or(SVaricap) -devExclud = Recog_diode.ext_or(SVaricap, nmoscl_2, nmoscl_4, npnMPA, schottky_nbl1, scr1, subst_tie_hole_w_npn, trans_bip) -SVaricap_or_schottky_nbl1 = SVaricap.ext_or(schottky_nbl1) -NGate_outside_SVaricap = NGate.ext_outside(SVaricap) -SVaricap_or_trans_bip = SVaricap.ext_or(trans_bip) -Cont_PAct_not_SVaricap = Cont_PAct.ext_not(SVaricap) -PAct_PWellHV_digi = PAct_PWellHV.not_outside(DigiBnd_hole) -Abut_PWell_Tie_NAct = NAct.ext_interacting(Abut_PWell_Tie) -Abut_NWell_Tie_Cont = Cont.inside(Abut_NWell_Tie_PAct) -lPWB_e_2 = PWell_block.ext_outside(schottky_nbl1_or_schottky_nw1) -SVaricap_Tie = PAct_PWell.not_outside(Activ.not_outside(SVaricap)) -lNw_c = PActLV.ext_not(PActLV.ext_interacting(SVaricap)) -NwellRing = NWell_Tie.ext_with_coincident_edges(NwellRing_edge) -nw_outDev = NAct_NWellHV_ana.ext_outside(SVaricap.ext_or(schottky_nbl1_or_schottky_nw1)) -pSD_c1_tmp1 = pSD.ext_outside(pSD_c1_exclusion) -temp_layer_4 = Cont_SQ.ext_not(SVaricap_or_trans_bip) -PAct_PWellHV_ana = PAct_PWellHV.ext_not(PAct_PWellHV_digi) -Abut_PWell_Tie_Cont = Cont.inside(Abut_PWell_Tie_NAct) -PWell_Tie_w_rf = PAct_PWell.ext_not(Recog_esd.ext_or(SalBlock, SubContDev, SubContDev_iso, cmim_tie, schottky_nbl1, schottky_nbl1_b, schottky_nw1, schottky_nw1_b)) -Holes_NwellRing = NwellRing.holes.merge -PwellRing_innermost = PWell_Tie_w_rf.holes.merge.outside(PWell_Tie_w_rf) -NoHoles_NwellRing = Holes_NwellRing.ext_or(NwellRing) -NGate_inside_PwellRing = NGate.not_outside(PwellRing_innermost) -PwellRing_edge = PWell_Tie_w_rf.ext_coincident_part(PwellRing_innermost, outside: true) -rfNwellRing = NoHoles_NwellRing.ext_interacting_with_text(TEXT, "rfpmos*") -PwellRing = PWell_Tie_w_rf.ext_with_coincident_edges(PwellRing_edge) -rfpmos_all = PGate_inside_NwellRing.not_outside(rfNwellRing) -Holes_PwellRing = PwellRing.holes.merge -NoHoles_PwellRing = Holes_PwellRing.ext_or(PwellRing) -pmosHV = PGate.ext_or(rfpmos_all).ext_not(MOSvaricap).not_outside(ThickGateOx) -rfPwellRing = NoHoles_PwellRing.ext_interacting_with_text(TEXT, "rfnmos*") -pnpMPARing = NoHoles_PwellRing.ext_interacting_with_text(TEXT, "pnpMPA") -rfnmos_all = NGate_inside_PwellRing.not_outside(rfPwellRing) -pnpMPA = PAct_NWell.not_outside(nBuLay).not_outside(pnpMPARing) -BJT_hole = (BJT_ring_a.holes - BJT_ring_a.with_holes).without_holes.ext_covering(TRANS.ext_or(pnpMPA)) -nmosHV = NGate.ext_or(rfnmos_all).ext_not(MOSvaricap).not_outside(ThickGateOx) -BJT_ring = BJT_ring_a.ext_interacting(BJT_hole) -PWell_Tie_wo_varicap_abut = PAct_PWell.ext_interacting(Abut_PWell_Tie.ext_or(BJT_ring, SVaricap_Tie), inverted: true) --> do - NWell_NW_a.dup -end.().output("NW.a", "Min. NWell width = 0.62") --> do - lNw_c.ext_enclosed(NWell_Nsram, 0.31.um, consider_overlaps: true) -end.().output("NW.c", "Min. NWell enclosure of P+Activ not inside ThickGateOx = 0.31") --> do - PActHV_ana.ext_outside(SVaricap_or_schottky_nbl1).ext_enclosed(NWell, 0.62.um, consider_overlaps: true) -end.().output("NW.c1", "Min. NWell enclosure of P+Activ inside ThickGateOx = 0.62") --> do - NWell_Nsram.ext_separation(NActLV, 0.31.um, consider_overlaps: true) -end.().output("NW.d", "Min. NWell space to external N+Activ not inside ThickGateOx = 0.31") --> do - NWell.ext_separation(NActHV_ana, 0.62.um) -end.().output("NW.d1", "Min. NWell space to external N+Activ inside ThickGateOx = 0.62") --> do - NAct_NWellLV.ext_not(nw_outDev).ext_enclosed(NWell, 0.24.um, consider_overlaps: true) -end.().output("NW.e", "Min. NWell enclosure of NWell tie surrounded entirely by NWell in N+Activ not inside ThickGateOx = 0.24") --> do - NAct_NWellHV_ana.ext_outside(SVaricap.ext_or(schottky_nbl1, schottky_nw1, scr1)).ext_enclosed(NWell, 0.62.um, consider_overlaps: true) -end.().output("NW.e1", "Min. NWell enclosure of NWell tie surrounded entirely by NWell in N+Activ inside ThickGateOx = 0.62") --> (;x, y) do - x = PAct_PWellLV.ext_coincident_edges(SVaricap, outside: true) - y = PAct_PWellLV.ext_with_coincident_edges(x) - NWell.ext_separation(PAct_PWellLV.ext_not(y), 0.24.um) -end.().output("NW.f", "Min. NWell space to substrate tie in P+Activ not inside ThickGateOx = 0.24") --> do - NWell.ext_separation(PAct_PWellHV_ana.ext_interacting(SVaricap, inverted: true), 0.62.um) -end.().output("NW.f1", "Min. NWell space to substrate tie in P+Activ inside ThickGateOx = 0.62") --> do - PWell_block_PWB_a.dup -end.().output("PWB.a", "Min. PWell:block width = 0.62") --> do - PWell_block_PWB_b.dup -end.().output("PWB.b", "Min. PWell:block space or notch = 0.62") --> do - PWellBlock_unrelatedNWell.ext_separation(PWell_block, 0.62.um, consider_touch_points: false, include_min_angle: false) -end.().output("PWB.c", "Min. PWell:block space to NWell = 0.62") --> do - lPWB_e_2.ext_separation(lPWB_e_1.ext_outside(ThickGateOx).ext_outside(NWell), 0.31.um, inside_edges_are_errors: true) -end.().output("PWB.e", "Min. PWell:block space to (N+Activ not inside ThickGateOx) in PWell = 0.31") --> do - lPWB_e_2.ext_separation(lPWB_e_1.inside(ThickGateOx).ext_outside(NWell), 0.62.um, inside_edges_are_errors: true) -end.().output("PWB.e1", "Min. PWell:block space to (N+Activ inside ThickGateOx) in PWell = 0.62") --> do - lPWB_e_2.ext_separation(lPWB_f_1.ext_outside(NWell).ext_outside(ThickGateOx), 0.24.um, inside_edges_are_errors: true) -end.().output("PWB.f", "Min. PWell:block space to (P+Activ not inside ThickGateOx) in PWell = 0.24") --> do - lPWB_e_2.ext_separation(lPWB_f_1.inside(ThickGateOx).ext_outside(NWell), 0.62.um, inside_edges_are_errors: true) -end.().output("PWB.f1", "Min. PWell:block space to (P+Activ inside ThickGateOx) in PWell = 0.62") --> do - nBuLayGen_nBuLay_NBL_a.dup -end.().output("NBL.a", "Min. nBuLay width = 1.00") --> do - nBuLay_e.dup -end.().output("NBL.e", "Min. nBuLay space to unrelated N+Activ = 1.00") --> do - nBuLay_block_NBLB_a.dup -end.().output("NBLB.a", "Min. nBuLay:block width = 1.50") --> do - nBuLay_block.ext_space(1.0.um) -end.().output("NBLB.b", "Min. nBuLay:block space or notch = 1.00") --> do - nBuLay_nBuLay_block_enc.dup -end.().output("NBLB.c", "Min. nBuLay enclosure of nBuLay:block = 1.00") --> do - nBuLay_block.ext_separation(nBuLay, 1.5.um, consider_touch_points: false) -end.().output("NBLB.d", "Min. nBuLay:block space to unrelated nBuLay = 1.50") --> do - Activ_Act_a.dup -end.().output("Act.a", "Min. Activ width = 0.15") --> do - Act_Nsram.ext_space(0.21.um) -end.().output("Act.b", "Min. Activ space or notch = 0.21") --> do - GatPoly.ext_enclosed(Act_Nsram, 0.23.um, metric: projection) -end.().output("Act.c", "Min. Activ drain/source extension = 0.23") --> do - Activ_Act_d.dup -end.().output("Act.d", "Min. Activ area (µm²) = 0.122") --> do - (Activ.holes - Activ.with_holes).without_holes.ext_not(Activ).ext_with_area([["<", 0.15.um2]]) -end.().output("Act.e", "Min. Activ enclosed area (µm²) = 0.15") - -if $fillerRules - -> do - Activ_filler.sized(-5.0.um/2.0, acute_limit).sized(5.0.um/2.0, acute_limit) - end.().output("AFil.a", "Max. Activ:filler width = 5.00") - -> do - Activ_filler.ext_width(1.0.um) - end.().output("AFil.a1", "Min. Activ:filler width = 1.00") - -> do - Activ_filler.ext_space(0.42.um) - end.().output("AFil.b", "Min. Activ:filler space = 0.42") - -> do - Activ_filler.ext_separation(Activ, 0.42.um, inside_edges_are_errors: true) - end.().output("AFil.c1", "Min. Activ:filler space to Activ = 0.42") - -> do - Activ_filler.ext_separation(TRANS, 1.0.um, inside_edges_are_errors: true) - end.().output("AFil.e", "Min. Activ:filler space to TRANS = 1.00") -end - - -if $densityRules - -> do - Act_density.ext_without_density(0.35 .. 0.55, 'll') - end.().output("AFil.g/g1", "Global Activ density [%] = 35.00 .. 55.00") - -> do - Act_density.ext_without_density(0.25 .. 0.65, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("AFil.g2/g3", "Activ coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 65.00") -end - - -if $fillerRules - -> do - Activ_filler.ext_outside(PWell_block).ext_separation(PWell_block, 1.5.um, min_angle: 90, max_angle: 180, include_min_angle: false) - end.().output("AFil.i", "Min. Activ:filler space to edges of PWell:block = 1.50") -end - --> do - Activ.ext_enclosed(ThickGateOx, 0.27.um) -end.().output("TGO.a", "Min. ThickGateOx extension over Activ = 0.27") --> do - ThickGateOx_Act_out_ThickGateOx_TGO_b_sep.dup -end.().output("TGO.b", "Min. space between ThickGateOx and Activ outside thick gate oxide region = 0.27") --> (;a) do - a = Gate.ext_enclosed(ThickGateOx, 0.34.um, include_min_angle: false, polygon_output: true) - a.ext_and(Activ) -end.().output("TGO.c", "Min. ThickGateOx extension over GatPoly over Activ = 0.34") --> do - ThickGateOx_GP_out_ThickGateOx_TGO_d_sep.dup -end.().output("TGO.d", "Min. space between ThickGateOx and GatPoly over Activ outside thick gate oxide region = 0.34") --> do - ThickGateOx_TGO_e.dup -end.().output("TGO.e", "Min. ThickGateOx space (merge if less than this value) = 0.86") --> do - ThickGateOx_TGO_f.dup -end.().output("TGO.f", "Min. ThickGateOx width = 0.86") --> do - GP_Nsram_Gat_a.dup -end.().output("Gat.a", "Min. GatPoly width = 0.13") --> (;a) do - a = Activ.ext_not(nmosHV).ext_interacting(nmosHV).ext_space(0.45.um, metric: projection, consider_touch_points: false, polygon_output: true) - a.ext_and(Activ).ext_outside(nmoscl.ext_or(scr1)) -end.().output("Gat.a3", "Min. GatPoly width for channel length of 3.3 V NFET = 0.45") --> (;b) do - b = Activ.ext_not(pmosHV).ext_interacting(pmosHV).ext_space(0.4.um, metric: projection, consider_touch_points: false, polygon_output: true) - b.ext_and(Activ) -end.().output("Gat.a4", "Min. GatPoly width for channel length of 3.3 V PFET = 0.4") --> do - GP_Nsram_Gat_b.dup -end.().output("Gat.b", "Min. GatPoly space or notch = 0.18") --> do - GP_mosHV_Gat_b1.dup -end.().output("Gat.b1", "Min. space between unrelated 3.3 V GatPoly over Activ regions = 0.25") --> do - [ Activ.ext_enclosed(GP_Nsram, 0.18.um), - GatPoly.inside(Activ) - ].each { |result| result.output("Gat.c", "Min. GatPoly extension over Activ (end cap) = 0.18") } -end.() --> do - GP_Nsram.ext_separation(Act_Nsram, 0.07.um) -end.().output("Gat.d", "Min. GatPoly space to Activ = 0.07") --> do - GatPoly_Gat_e.dup -end.().output("Gat.e", "Min. GatPoly area (µm²) = 0.09") --> do - Gate.ext_not(SVaricap).ext_rectangles(true, false, nil, nil, nil, inverted: true) -end.().output("Gat.f", "45-degree and 90-degree angles for GatPoly on Activ area are not allowed") - -if $fillerRules - -> do - GatPoly_filler.sized(-5.0.um/2.0, acute_limit).sized(5.0.um/2.0, acute_limit) - end.().output("GFil.a", "Max. GatPoly:filler width = 5.00") - -> do - GatPoly_filler.ext_width(0.7.um, consider_touch_points: false) - end.().output("GFil.b", "Min. GatPoly:filler width = 0.70") - -> do - GatPoly_filler.ext_space(0.8.um) - end.().output("GFil.c", "Min. GatPoly:filler space = 0.80") - -> do - Activ.ext_separation(GatPoly_filler, 1.1.um, max_angle: 180) - end.().output("GFil.d.Activ", "Min. GatPoly:filler space to Activ = 1.10") - -> do - GatPoly.ext_separation(GatPoly_filler, 1.1.um, max_angle: 180) - end.().output("GFil.d.GatPoly", "Min. GatPoly:filler space to GatPoly = 1.10") - -> do - Cont.ext_separation(GatPoly_filler, 1.1.um, max_angle: 180) - end.().output("GFil.d.Cont", "Min. GatPoly:filler space to Cont = 1.10") - -> do - pSD.ext_separation(GatPoly_filler, 1.1.um, max_angle: 180) - end.().output("GFil.d.pSD", "Min. GatPoly:filler space to pSD = 1.10") - -> do - nSD_block.ext_separation(GatPoly_filler, 1.1.um, max_angle: 180) - end.().output("GFil.d.nSD_block", "Min. GatPoly:filler space to nSD:block = 1.10") - -> do - SalBlock.ext_separation(GatPoly_filler, 1.1.um, max_angle: 180) - end.().output("GFil.d.SalBlock", "Min. GatPoly:filler space to SalBlock = 1.10") - -> do - GatPoly_filler.ext_separation(TRANS, 1.1.um) - end.().output("GFil.f", "Min. GatPoly:filler space to TRANS = 1.10") -end - - -if $densityRules - -> do - Gat_density.ext_with_density(0.0 .. 0.15, 'll') - end.().output("GFil.g", "Min. global GatPoly density [%] = 15.00") -end - - -if $fillerRules - -> (;shpX, shpY, shp_err) do - shpX = GatPoly_nofill.sized(-(400*0.5).um, 0) - shpY = GatPoly_nofill.sized(0, -(400*0.5).um) - shp_err = shpX.sized((0.18*0.5).um, acute_limit).and(GatPoly_nofill).ext_or(shpY.sized((0.18*0.5).um, acute_limit).and(GatPoly_nofill)) - GatPoly_nofill.ext_covering(shp_err) - end.().output("GFil.i", "Max. GatPoly:nofill area (µm²) = 400 x 400") - -> do - Activ_filler.ext_enclosed(GatPoly_filler, 0.18.um) - end.().output("GFil.j", "Min. GatPoly:filler extension over Activ:filler (end cap) = 0.18") -end - --> do - pSD_pSD_a.dup -end.().output("pSD.a", "Min. pSD width = 0.31") --> do - pSD.ext_space(0.31.um) -end.().output("pSD.b", "Min. pSD space or notch (pSD regions separated by less than this value will be merged.) = 0.31") --> do - Act_NWell.ext_enclosed(pSD_c_tmp1, 0.18.um) -end.().output("pSD.c", "Min. pSD enclosure of P+Activ in NWell = 0.18") --> do - Act_PWell.ext_enclosed(pSD_c1_tmp1, 0.03.um) -end.().output("pSD.c1", "Min. pSD enclosure of P+Activ in PWell = 0.03") --> do - pSD.ext_separation(NAct_PWell, 0.18.um, consider_intersecting_edges: false) -end.().output("pSD.d", "Min. pSD space to unrelated N+Activ in PWell = 0.18") --> do - pSD.ext_separation(NAct_NWell, 0.03.um, consider_intersecting_edges: false) -end.().output("pSD.d1", "Min. pSD space to N+Activ in NWell = 0.03") --> (;layA, layB, layC, layD) do - layA = Act_Nsram.not_inside(pSD).ext_interacting(pSD) - layB = layA.ext_and(pSD).ext_outside(SVaricap) - layC = layB.ext_width(0.3.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - layD = layC.ext_covering(layB) - layD.dup -end.().output("pSD.e", "Min. pSD overlap of Activ at one position when forming abutted substrate tie (These rules are for abutted ties: An electrical connection from P+Activ to NWell tie (or N+ Activ to P-sub tie) is made through the source/drain silicide. For a good electrical connection rule pSD.g is important together with rule pSD.e or pSD.f (see Fig. 5.10).) = 0.30") --> (;abuttedNTAP, bad_region, good_region) do - abuttedNTAP = NAct_NWell.ext_interacting(PAct_NWell) - bad_region = abuttedNTAP.ext_coincident_part(PAct_NWell, outside: true).ext_overlap(NAct_NWell, 0.3.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - good_region = abuttedNTAP.ext_not(bad_region) - abuttedNTAP.ext_outside(good_region) -end.().output("pSD.f", "Min. Activ extension over pSD at one position when forming abutted NWell tie (These rules are for abutted ties: An electrical connection from P+Activ to NWell tie (or N+ Activ to P-sub tie) is made through the source/drain silicide. For a good electrical connection rule pSD.g is important together with rule pSD.e or pSD.f (see Fig. 5.10).) = 0.30") --> (;x, y) do - x = NAct_NWell_not_Gate.ext_interacting(SVaricap_or_schottky_nbl1, inverted: true).ext_outside(SRAM) - y = PAct_PWell_not_Gate.ext_interacting(SVaricap_or_schottky_nbl1, inverted: true).ext_outside(SRAM) - [ x.ext_interacting(Gate, inverted: true).ext_with_area([["<", 0.09.um2]]), - y.ext_interacting(Gate, inverted: true).ext_with_area([["<", 0.09.um2]]) - ].each { |result| result.output("pSD.g", "Min. N+Activ or P+Activ area (µm²) when forming abutted tie (These rules are for abutted ties: An electrical connection from P+Activ to NWell tie (or N+ Activ to P-sub tie) is made through the source/drain silicide. For a good electrical connection rule pSD.g is important together with rule pSD.e or pSD.f (see Fig. 5.10).) = 0.09") } -end.() --> do - PGate.ext_enclosed(pSD_Nsram, 0.3.um) -end.().output("pSD.i", "Min. pSD enclosure of PFET gate not inside ThickGateOx = 0.30") --> do - PGate.ext_enclosed(pSDHV_Nsram, 0.4.um) -end.().output("pSD.i1", "Min. pSD enclosure of PFET gate inside ThickGateOx = 0.40") --> do - pSD_Nsram.ext_separation(NGate_outside_SVaricap, 0.3.um) -end.().output("pSD.j", "Min. pSD space to NFET gate not inside ThickGateOx = 0.30") --> do - pSD_Nsram.ext_separation(NGate_outside_SVaricap.inside(ThickGateOx), 0.4.um) -end.().output("pSD.j1", "Min. pSD space to NFET gate inside ThickGateOx = 0.40") --> do - pSD_pSD_k.dup -end.().output("pSD.k", "Min. pSD area (µm²) = 0.25") --> do - pSDL_enc_area_pSD_l.dup -end.().output("pSD.l", "Min. pSD enclosed area (µm²) = 0.25") --> do - GP_Rsil_extended_external_pSD.dup -end.().output("pSD.m", "Min. pSD space to n-type poly resistors = 0.18") --> do - Rppd_all_enclosure_pSD.dup -end.().output("pSD.n", "Min. pSD enclosure of p-type poly resistors = 0.18") --> do - nSD_block.ext_width(0.31.um) -end.().output("nSDB.a", "Min. nSD:block width = 0.31") --> do - nSD_block.ext_space(0.31.um) -end.().output("nSDB.b", "Min. nSD:block space or notch = 0.31") --> do - nSD_block.ext_separation(pSD.ext_interacting(nSD_block, inverted: true), 0.31.um, consider_touch_points: false) -end.().output("nSDB.c", "Min. nSD:block space to pSD = 0.31") --> do - Cont.ext_outside(nsdb_exlcDev).ext_and(nSD_block) -end.().output("nSDB.e", "Min. nSD:block space to Cont (nSD:block and Cont do not overlap.) = 0.00") --> do - EXTBlock.ext_width(0.31.um) -end.().output("EXTB.a", "Min. EXTBlock width = 0.31") --> do - EXTBlock.ext_space(0.31.um) -end.().output("EXTB.b", "Min. EXTBlock space or notch = 0.31") --> do - EXTBlock.ext_separation(pSD, 0.31.um) -end.().output("EXTB.c", "Min. EXTBlock space to pSD = 0.31") --> do - SalBlock.ext_width(0.42.um) -end.().output("Sal.a", "Min. SalBlock width = 0.42") --> do - SalBlock.ext_space(0.42.um) -end.().output("Sal.b", "Min. SalBlock space or notch = 0.42") --> do - [ GatPoly_res.ext_enclosed(SalBlock, 0.2.um), - Activ.ext_enclosed(SalBlock, 0.2.um) - ].each { |result| result.output("Sal.c", "Min. SalBlock extension over Activ or GatPoly = 0.20") } -end.() --> do - [ SalBlock.ext_separation(GatPoly_res, 0.2.um, consider_touch_points: false), - SalBlock.ext_separation(nmosi_relevant_activ, 0.2.um, consider_touch_points: false) - ].each { |result| result.output("Sal.d", "Min. SalBlock space to unrelated Activ or GatPoly = 0.20") } -end.() --> do - SalBlock.ext_separation(Cont, 0.2.um) -end.().output("Sal.e", "Min. SalBlock space to Cont = 0.20") --> do - Cont_outside_EdgeSeal.ext_not(ContBar.ext_or(Cont_SQ)) -end.().output("Cnt.a", "Min. and max. Cont width = 0.16") --> do - Cont_outside_EdgeSeal.ext_space(0.18.um, consider_intersecting_edges: false) -end.().output("Cnt.b", "Min. Cont space = 0.18") --> (;x1, viaLargeArray, viaInLargeArray, viaInLargeArray_error, badViaLine) do - x1 = Cont.sized((0.20*0.5).um, acute_limit).sized(-(0.20*0.5).um, acute_limit) - viaLargeArray = x1.sized(-(((5*0.16)+(3*0.18))/2-0.001).um, acute_limit).sized((((5*0.16)+(3*0.18))/2-0.001).um, acute_limit) - viaInLargeArray = Cont.inside(viaLargeArray) - viaInLargeArray_error = viaInLargeArray.sized((0.20/2-0.001).um, acute_limit).sized(-(0.20/2-0.001).um, acute_limit) - badViaLine = viaInLargeArray_error.ext_not(viaInLargeArray) - badViaLine.ext_rectangles(inverted: true) -end.().output("Cnt.b1", "Min. Cont space in a contact array of more than 4 rows and more then 4 columns (Cnt.b1 is only required in one direction. The distance of the other direction must be at least Cnt.b.) = 0.20") --> do - temp_layer_4.ext_enclosed(Act_Nsram_or_Activ_mask.ext_not(DigiBnd), 0.07.um, consider_overlaps: true) -end.().output("Cnt.c", "Min. Activ enclosure of Cont = 0.07") --> do - Cont_SQ.ext_enclosed(GP_Nsram, 0.07.um, consider_overlaps: true) -end.().output("Cnt.d", "Min. GatPoly enclosure of Cont = 0.07") --> do - Cont_GP.ext_not(SVaricap).ext_separation(Activ, 0.14.um, consider_intersecting_edges: false, consider_touch_points: false) -end.().output("Cnt.e", "Min. Cont on GatPoly space to Activ = 0.14") --> do - Cont_Act.ext_not(SVaricap).ext_separation(GP_Nsram, 0.11.um) -end.().output("Cnt.f", "Min. Cont on Activ space to GatPoly = 0.11") --> do - Cont_not_Act_GP.dup -end.().output("Cnt.g", "Cont must be within Activ or GatPoly") --> do - pSD.ext_separation(Cont_NAct.ext_not(SVaricap), 0.09.um, consider_intersecting_edges: false, consider_touch_points: false) -end.().output("Cnt.g1", "Min. pSD space to Cont on nSD-Activ = 0.09") --> do - Cont_PAct_not_SVaricap.ext_enclosed(pSD_Nsram, 0.09.um, consider_intersecting_edges: false, consider_touch_points: false) -end.().output("Cnt.g2", "Min. pSD overlap of Cont on pSD-Activ = 0.09") --> do - Cont_not_M1.dup -end.().output("Cnt.h", "Cont must be covered with Metal1") --> do - Cont_Act_GP.ext_not(SVaricap) -end.().output("Cnt.j", "Cont on GatPoly over Activ is not allowed") --> do - [ ContBar.ext_outside(EdgeSeal).ext_not(schottky_nbl1_or_schottky_nw1).ext_width(0.16.um), - Cont_outside_EdgeSeal.ext_not(schottky_nbl1_or_schottky_nw1).sized(-0.16.um/2.0, acute_limit).sized(0.16.um/2.0, acute_limit) - ].each { |result| result.output("CntB.a", "Min. and max. ContBar width = 0.16") } -end.() --> do - CntB_a1_error.dup -end.().output("CntB.a1", "Min. ContBar length = 0.34") --> do - ContBar_outside_TRANS.ext_space(0.28.um) -end.().output("CntB.b", "Min. ContBar space = 0.28") --> (;l1) do - l1 = ContBar.ext_space(0.36.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(5.001.um, 0.001.um) -end.().output("CntB.b1", "Min. ContBar space with common run > 5 µm = 0.36") --> do - ContBar.ext_separation(Cont_SQ, 0.22.um) -end.().output("CntB.b2", "Min. ContBar space to Cont = 0.22") --> do - ContBar.ext_outside(trans_bip).ext_enclosed(nmosi_relevant_activ, 0.07.um, consider_overlaps: true) -end.().output("CntB.c", "Min. Activ enclosure of ContBar = 0.07") --> do - ContBar.ext_enclosed(GatPoly, 0.07.um, consider_overlaps: true) -end.().output("CntB.d", "Min. GatPoly enclosure of ContBar = 0.07") --> do - ContBar_GP.ext_separation(Activ, 0.14.um) -end.().output("CntB.e", "Min. ContBar on GatPoly space to Activ = 0.14") --> do - ContBar_Act.ext_separation(GatPoly, 0.11.um) -end.().output("CntB.f", "Min. ContBar on Activ space to GatPoly = 0.11") --> do - ContBar_not_Act_GP.dup -end.().output("CntB.g", "ContBar must be within Activ or GatPoly") --> do - pSD.ext_separation(ContBar_NAct, 0.09.um, max_angle: 0, include_max_angle: true, polygon_output: true) -end.().output("CntB.g1", "Min. pSD space to ContBar on nSD-Activ = 0.09") --> do - ContBar_PAct.ext_enclosed(pSD, 0.09.um) -end.().output("CntB.g2", "Min. pSD overlap of ContBar on pSD-Activ = 0.09") --> do - ContBar_not_M1.dup -end.().output("CntB.h", "ContBar must be covered with Metal1") --> do - ContBar_outside_TRANS.ext_enclosed(Metal1_Nslit, 0.05.um, outside_edges_are_errors: true) -end.().output("CntB.h1", "Min. Metal1 enclosure of ContBar = 0.05") --> do - ContBar_Act_GP.dup -end.().output("CntB.j", "ContBar on GatPoly over Activ is not allowed") --> do - Metal1_Nslit.ext_width(0.16.um) -end.().output("M1.a", "Min. Metal1 width = 0.16") --> do - M1_Nsram.ext_space(0.18.um) -end.().output("M1.b", "Min. Metal1 space or notch = 0.18") --> do - Cont_Nsram.ext_not(M1_Nsram) -end.().output("M1.c", "Min. Metal1 enclosure of Cont = 0.00") --> do - [Cont_Nsram.ext_outside(EdgeSeal), Metal1_outside_EdgeSeal].then { |layer1, layer2| layer1.drc(if_any( - !rectangles, - primary-secondary(layer2), - ((enclosed(layer2, projection, whole_edges, one_side_allowed, two_opposite_sides_allowed) < 0.05.um)))) } -end.().output("M1.c1", "Min. Metal1 endcap enclosure of Cont (For contacts at Metal1 corners at least one side must be treated as an endcap and for the other sides rule M1.c can be applied.) = 0.05") --> do - Metal1_outside_EdgeSeal.ext_with_area([["<", 0.09.um2]]) -end.().output("M1.d", "Min. Metal1 area (µm²) = 0.09") --> (;wide_Metal1, l1) do - wide_Metal1 = Metal1_outside_EdgeSeal.sized(-0.3.um/2.0, acute_limit).sized(0.3.um/2.0, acute_limit) - l1 = Metal1_outside_EdgeSeal.ext_separation(wide_Metal1, 0.22.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(1.001.um, 0.001.um) -end.().output("M1.e", "Min. space of Metal1 lines if, at least one line is wider than 0.3 µm and the parallel run is more than 1.0 µm = 0.22") --> (;wide_Metal1, l1) do - wide_Metal1 = Metal1_outside_EdgeSeal.sized(-10.0.um/2.0, acute_limit).sized(10.0.um/2.0, acute_limit) - l1 = Metal1_outside_EdgeSeal.ext_separation(wide_Metal1, 0.6.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(10.001.um, 0.001.um) -end.().output("M1.f", "Min. space of Metal1 lines if, at least one line is wider than 10.0 µm and the parallel run is more than 10.0 µm = 0.60") - -if $densityRules - -> do - M1_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M1.j/k", "Global Metal1 density [%] = 35.0 .. 60.0") -end - --> do - Metal2_Nslit.ext_width(0.2.um) -end.().output("M2.a", "Min. Metal2 width = 0.20") --> do - M2_Nsram.ext_space(0.21.um) -end.().output("M2.b", "Min. Metal2 space or notch = 0.21") --> do - Via1.ext_outside(EdgeSeal).ext_enclosed(Metal2_outside_EdgeSeal, 0.005.um, max_angle: 180) -end.().output("M2.c", "Min. Metal2 enclosure of Via1 = 0.005") --> do - V1_Nsram_outside_EdgeSeal.drc(if_any( - !rectangles, - primary-secondary(Metal2_outside_EdgeSeal), - ((enclosed(Metal2_outside_EdgeSeal, projection, whole_edges, one_side_allowed, two_opposite_sides_allowed) < 0.05.um)))) -end.().output("M2.c1", "Min. Metal2 endcap enclosure of Via1 (For vias at Metal2 corners at least one side must be treated as an endcap and for the other sides rule M2.c can be applied.) = 0.05") --> do - Metal2_outside_EdgeSeal.ext_with_area([["<", 0.144.um2]]) -end.().output("M2.d", "Min. Metal2 area (µm²) = 0.144") --> (;wide_Metal2, l1) do - wide_Metal2 = Metal2_outside_EdgeSeal.sized(-0.39.um/2.0, acute_limit).sized(0.39.um/2.0, acute_limit) - l1 = Metal2_outside_EdgeSeal.ext_separation(wide_Metal2, 0.24.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(1.001.um, 0.001.um) -end.().output("M2.e", "Min. space of Metal2 lines if, at least one line is wider than 0.39 µm and the parallel run is more than 1.0 µm = 0.24") --> (;wide_Metal2, l1) do - wide_Metal2 = Metal2_outside_EdgeSeal.sized(-10.0.um/2.0, acute_limit).sized(10.0.um/2.0, acute_limit) - l1 = Metal2_outside_EdgeSeal.ext_separation(wide_Metal2, 0.6.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(10.001.um, 0.001.um) -end.().output("M2.f", "Min. space of Metal2 lines if, at least one line is wider than 10.0 µm and the parallel run is more than 10.0 µm = 0.60") - -if $densityRules - -> do - M2_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M2.j/k", "Global Metal2 density [%] = 35.00 .. 60.00") -end - --> do - Metal3_Nslit.ext_width(0.2.um) -end.().output("M3.a", "Min. Metal3 width = 0.20") --> do - M3_Nsram.ext_space(0.21.um) -end.().output("M3.b", "Min. Metal3 space or notch = 0.21") --> do - Via2.ext_outside(EdgeSeal).ext_enclosed(Metal3_outside_EdgeSeal, 0.005.um, max_angle: 180) -end.().output("M3.c", "Min. Metal3 enclosure of Via2 = 0.005") --> do - V2_Nsram_outside_EdgeSeal.drc(if_any( - !rectangles, - primary-secondary(Metal3_outside_EdgeSeal), - ((enclosed(Metal3_outside_EdgeSeal, projection, whole_edges, one_side_allowed, two_opposite_sides_allowed) < 0.05.um)))) -end.().output("M3.c1", "Min. Metal3 endcap enclosure of Via2 (For vias at Metal3 corners at least one side must be treated as an endcap and for the other sides rule M3.c can be applied.) = 0.05") --> do - Metal3_outside_EdgeSeal.ext_with_area([["<", 0.144.um2]]) -end.().output("M3.d", "Min. Metal3 area (µm²) = 0.144") --> (;wide_Metal3, l1) do - wide_Metal3 = Metal3_outside_EdgeSeal.sized(-0.39.um/2.0, acute_limit).sized(0.39.um/2.0, acute_limit) - l1 = Metal3_outside_EdgeSeal.ext_separation(wide_Metal3, 0.24.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(1.001.um, 0.001.um) -end.().output("M3.e", "Min. space of Metal3 lines if, at least one line is wider than 0.39 µm and the parallel run is more than 1.0 µm = 0.24") --> (;wide_Metal3, l1) do - wide_Metal3 = Metal3_outside_EdgeSeal.sized(-10.0.um/2.0, acute_limit).sized(10.0.um/2.0, acute_limit) - l1 = Metal3_outside_EdgeSeal.ext_separation(wide_Metal3, 0.6.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(10.001.um, 0.001.um) -end.().output("M3.f", "Min. space of Metal3 lines if, at least one line is wider than 10.0 µm and the parallel run is more than 10.0 µm = 0.60") - -if $densityRules - -> do - M3_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M3.j/k", "Global Metal3 density [%] = 35.00 .. 60.00") -end - --> do - Metal4_Nslit.ext_width(0.2.um) -end.().output("M4.a", "Min. Metal4 width = 0.20") --> do - M4_Nsram.ext_space(0.21.um) -end.().output("M4.b", "Min. Metal4 space or notch = 0.21") --> do - Via3.ext_outside(EdgeSeal).ext_enclosed(Metal4_outside_EdgeSeal, 0.005.um, max_angle: 180) -end.().output("M4.c", "Min. Metal4 enclosure of Via3 = 0.005") --> do - V3_Nsram_outside_EdgeSeal.drc(if_any( - !rectangles, - primary-secondary(Metal4_outside_EdgeSeal), - ((enclosed(Metal4_outside_EdgeSeal, projection, whole_edges, one_side_allowed, two_opposite_sides_allowed) < 0.05.um)))) -end.().output("M4.c1", "Min. Metal4 endcap enclosure of Via3 (For vias at Metal4 corners at least one side must be treated as an endcap and for the other sides rule M4.c can be applied.) = 0.05") --> do - Metal4_outside_EdgeSeal.ext_with_area([["<", 0.144.um2]]) -end.().output("M4.d", "Min. Metal4 area (µm²) = 0.144") --> (;wide_Metal4, l1) do - wide_Metal4 = Metal4_outside_EdgeSeal.sized(-0.39.um/2.0, acute_limit).sized(0.39.um/2.0, acute_limit) - l1 = Metal4_outside_EdgeSeal.ext_separation(wide_Metal4, 0.24.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(1.001.um, 0.001.um) -end.().output("M4.e", "Min. space of Metal4 lines if, at least one line is wider than 0.39 µm and the parallel run is more than 1.0 µm = 0.24") --> (;wide_Metal4, l1) do - wide_Metal4 = Metal4_outside_EdgeSeal.sized(-10.0.um/2.0, acute_limit).sized(10.0.um/2.0, acute_limit) - l1 = Metal4_outside_EdgeSeal.ext_separation(wide_Metal4, 0.6.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(10.001.um, 0.001.um) -end.().output("M4.f", "Min. space of Metal4 lines if, at least one line is wider than 10.0 µm and the parallel run is more than 10.0 µm = 0.60") - -if $densityRules - -> do - M4_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M4.j/k", "Global Metal4 density [%] = 35.00 .. 60.00") -end - --> do - Metal5_Nslit.ext_width(0.2.um) -end.().output("M5.a", "Min. Metal5 width = 0.20") --> do - M5_Nsram.ext_space(0.21.um) -end.().output("M5.b", "Min. Metal5 space or notch = 0.21") --> do - Via4.ext_outside(EdgeSeal).ext_enclosed(Metal5_outside_EdgeSeal, 0.005.um, max_angle: 180) -end.().output("M5.c", "Min. Metal5 enclosure of Via4 = 0.005") --> do - V4_Nsram_outside_EdgeSeal.drc(if_any( - !rectangles, - primary-secondary(Metal5_outside_EdgeSeal), - ((enclosed(Metal5_outside_EdgeSeal, projection, whole_edges, one_side_allowed, two_opposite_sides_allowed) < 0.05.um)))) -end.().output("M5.c1", "Min. Metal5 endcap enclosure of Via4 (For vias at Metal5 corners at least one side must be treated as an endcap and for the other sides rule M5.c can be applied.) = 0.05") --> do - Metal5_outside_EdgeSeal.ext_with_area([["<", 0.144.um2]]) -end.().output("M5.d", "Min. Metal5 area (µm²) = 0.144") --> (;wide_Metal5, l1) do - wide_Metal5 = Metal5_outside_EdgeSeal.sized(-0.39.um/2.0, acute_limit).sized(0.39.um/2.0, acute_limit) - l1 = Metal5_outside_EdgeSeal.ext_separation(wide_Metal5, 0.24.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(1.001.um, 0.001.um) -end.().output("M5.e", "Min. space of Metal5 lines if, at least one line is wider than 0.39 µm and the parallel run is more than 1.0 µm = 0.24") --> (;wide_Metal5, l1) do - wide_Metal5 = Metal5_outside_EdgeSeal.sized(-10.0.um/2.0, acute_limit).sized(10.0.um/2.0, acute_limit) - l1 = Metal5_outside_EdgeSeal.ext_separation(wide_Metal5, 0.6.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(10.001.um, 0.001.um) -end.().output("M5.f", "Min. space of Metal5 lines if, at least one line is wider than 10.0 µm and the parallel run is more than 10.0 µm = 0.60") - -if $densityRules - -> do - M5_density.ext_without_density(0.35 .. 0.6, 'll') - end.().output("M5.j/k", "Global Metal5 density [%] = 35.00 .. 60.00") -end - - -if $fillerRules - -> do - Metal1_filler.ext_width(1.0.um) - end.().output("M1Fil.a1", "Min. Metal1:filler width = 1.00") - -> (;tmpX, tmpY) do - tmpX = Metal1_filler.sized(-(5.0*0.5).um, 0) - tmpY = Metal1_filler.sized(0, -(5.0*0.5).um) - Metal1_filler.ext_interacting(tmpX.ext_or(tmpY)).dup - end.().output("M1Fil.a2", "Max. Metal1:filler width = 5.00") - -> do - Metal1_filler.ext_space(0.42.um) - end.().output("M1Fil.b", "Min. Metal1:filler space = 0.42") - -> do - Metal1_filler.ext_separation(Metal1_Nslit, 0.42.um) - end.().output("M1Fil.c", "Min. Metal1:filler space to Metal1 = 0.42") - -> do - Metal1_filler.ext_separation(TRANS, 1.0.um) - end.().output("M1Fil.d", "Min. Metal1:filler space to TRANS = 1.00") -end - - -if $densityRules - -> do - M1_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M1Fil.h/k", "Metal1 and Metal1:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") -end - - -if $fillerRules - -> do - Metal2_filler.ext_width(1.0.um) - end.().output("M2Fil.a1", "Min. Metal2:filler width = 1.00") - -> (;tmpX, tmpY) do - tmpX = Metal2_filler.sized(-(5.0*0.5).um, 0) - tmpY = Metal2_filler.sized(0, -(5.0*0.5).um) - Metal2_filler.ext_interacting(tmpX.ext_or(tmpY)).dup - end.().output("M2Fil.a2", "Max. Metal2:filler width = 5.00") - -> do - Metal2_filler.ext_space(0.42.um) - end.().output("M2Fil.b", "Min. Metal2:filler space = 0.42") - -> do - Metal2_filler.ext_separation(Metal2_Nslit, 0.42.um) - end.().output("M2Fil.c", "Min. Metal2:filler space to Metal2 = 0.42") - -> do - Metal2_filler.ext_separation(TRANS, 1.0.um) - end.().output("M2Fil.d", "Min. Metal2:filler space to TRANS = 1.00") -end - - -if $densityRules - -> do - M2_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M2Fil.h/k", "Metal2 and Metal2:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") -end - - -if $fillerRules - -> do - Metal3_filler.ext_width(1.0.um) - end.().output("M3Fil.a1", "Min. Metal3:filler width = 1.00") - -> (;tmpX, tmpY) do - tmpX = Metal3_filler.sized(-(5.0*0.5).um, 0) - tmpY = Metal3_filler.sized(0, -(5.0*0.5).um) - Metal3_filler.ext_interacting(tmpX.ext_or(tmpY)).dup - end.().output("M3Fil.a2", "Max. Metal3:filler width = 5.00") - -> do - Metal3_filler.ext_space(0.42.um) - end.().output("M3Fil.b", "Min. Metal3:filler space = 0.42") - -> do - Metal3_filler.ext_separation(Metal3_Nslit, 0.42.um) - end.().output("M3Fil.c", "Min. Metal3:filler space to Metal3 = 0.42") - -> do - Metal3_filler.ext_separation(TRANS, 1.0.um) - end.().output("M3Fil.d", "Min. Metal3:filler space to TRANS = 1.00") -end - - -if $densityRules - -> do - M3_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M3Fil.h/k", "Metal3 and Metal3:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") -end - - -if $fillerRules - -> do - Metal4_filler.ext_width(1.0.um) - end.().output("M4Fil.a1", "Min. Metal4:filler width = 1.00") - -> (;tmpX, tmpY) do - tmpX = Metal4_filler.sized(-(5.0*0.5).um, 0) - tmpY = Metal4_filler.sized(0, -(5.0*0.5).um) - Metal4_filler.ext_interacting(tmpX.ext_or(tmpY)).dup - end.().output("M4Fil.a2", "Max. Metal4:filler width = 5.00") - -> do - Metal4_filler.ext_space(0.42.um) - end.().output("M4Fil.b", "Min. Metal4:filler space = 0.42") - -> do - Metal4_filler.ext_separation(Metal4_Nslit, 0.42.um) - end.().output("M4Fil.c", "Min. Metal4:filler space to Metal4 = 0.42") - -> do - Metal4_filler.ext_separation(TRANS, 1.0.um) - end.().output("M4Fil.d", "Min. Metal4:filler space to TRANS = 1.00") -end - - -if $densityRules - -> do - M4_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M4Fil.h/k", "Metal4 and Metal4:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") -end - - -if $fillerRules - -> do - Metal5_filler.ext_width(1.0.um) - end.().output("M5Fil.a1", "Min. Metal5:filler width = 1.00") - -> (;tmpX, tmpY) do - tmpX = Metal5_filler.sized(-(5.0*0.5).um, 0) - tmpY = Metal5_filler.sized(0, -(5.0*0.5).um) - Metal5_filler.ext_interacting(tmpX.ext_or(tmpY)).dup - end.().output("M5Fil.a2", "Max. Metal5:filler width = 5.00") - -> do - Metal5_filler.ext_space(0.42.um) - end.().output("M5Fil.b", "Min. Metal5:filler space = 0.42") - -> do - Metal5_filler.ext_separation(Metal5_Nslit, 0.42.um) - end.().output("M5Fil.c", "Min. Metal5:filler space to Metal5 = 0.42") - -> do - Metal5_filler.ext_separation(TRANS, 1.0.um) - end.().output("M5Fil.d", "Min. Metal5:filler space to TRANS = 1.00") -end - - -if $densityRules - -> do - M5_density.ext_without_density(0.25 .. 0.75, 'll', tile_size(800.0.um), tile_step(400.0.um), tiles_stay_inside: true) - end.().output("M5Fil.h/k", "Metal5 and Metal5:filler coverage ratio for any 800 x 800 µm² chip area [%] = 25.00 .. 75.00") -end - --> do - Via1_edgC1_out.ext_outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true) -end.().output("V1.a", "Min. and max. Via1 width = 0.19") --> do - Via1_edgC1_out.ext_space(0.22.um, consider_intersecting_edges: false) -end.().output("V1.b", "Min. Via1 space = 0.22") --> (;via1NoES, x1, via1Array, via1In, via1BigArray, via1SepErr_1, via1SepErr_2) do - via1NoES = Via1_edgC1_out.dup - x1 = via1NoES.sized((0.29*0.5).um, acute_limit).sized(-(0.29*0.5).um, acute_limit) - via1Array = x1.sized(-(((4*0.19+3*0.22)-0.05)*0.5).um, acute_limit).sized((((4*0.19+3*0.22)-0.05)*0.5).um, acute_limit) - via1In = via1NoES.inside(via1Array) - via1BigArray = via1In.sized(0.143.um, acute_limit).sized(-0.143.um, acute_limit) - via1SepErr_1 = via1BigArray.ext_not(via1In) - via1SepErr_2 = via1SepErr_1.ext_not(via1SepErr_1.ext_rectangles) - via1SepErr_2.ext_or(via1In.ext_touching(via1SepErr_2)) -end.().output("V1.b1", "Min. Via1 space in an array of more than 3 rows and more then 3 columns (V1.b1 is only required in one direction. The distance of the other direction must be at least V1.b.) = 0.29") --> do - Via1.ext_enclosed(Metal1_Nslit, 0.01.um, outside_edges_are_errors: true) -end.().output("V1.c", "Min. Metal1 enclosure of Via1 = 0.01") --> (;x) do - x = V1_Nsram_outside_EdgeSeal.ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil) - x.drc(if_any( - !rectangles, - primary-secondary(Metal1_outside_EdgeSeal), - (if_any(enclosed(Metal1_outside_EdgeSeal) < 0.01.um, enclosed(Metal1_outside_EdgeSeal, projection, whole_edges, one_side_allowed, two_opposite_sides_allowed) < 0.05.um)))) -end.().output("V1.c1", "Min. Metal1 endcap enclosure of Via1 (For Via1 at Metal1 corners at least one side must be treated as an endcap and for the other sides rule V1.c can be applied.) = 0.05") --> do - Via2_edgC1_out.ext_outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true) -end.().output("V2.a", "Min. and max. Via2 width = 0.19") --> do - Via2_edgC1_out.ext_space(0.22.um, consider_intersecting_edges: false) -end.().output("V2.b", "Min. Via2 space = 0.22") --> (;via2NoES, x1, via2Array, via2In, via2BigArray, via2SepErr_1, via2SepErr_2) do - via2NoES = Via2_edgC1_out.dup - x1 = via2NoES.sized((0.29*0.5).um, acute_limit).sized(-(0.29*0.5).um, acute_limit) - via2Array = x1.sized(-(((4*0.19+3*0.22)-0.05)*0.5).um, acute_limit).sized((((4*0.19+3*0.22)-0.05)*0.5).um, acute_limit) - via2In = via2NoES.inside(via2Array) - via2BigArray = via2In.sized(0.143.um, acute_limit).sized(-0.143.um, acute_limit) - via2SepErr_1 = via2BigArray.ext_not(via2In) - via2SepErr_2 = via2SepErr_1.ext_not(via2SepErr_1.ext_rectangles) - via2SepErr_2.ext_or(via2In.ext_touching(via2SepErr_2)) -end.().output("V2.b1", "Min. Via2 space in an array of more than 3 rows and more then 3 columns (V2.b1 is only required in one direction. The distance of the other direction must be at least V2.b.) = 0.29") --> do - Via2.ext_enclosed(Metal2_Nslit, 0.005.um, outside_edges_are_errors: true) -end.().output("V2.c", "Min. Metal2 enclosure of Via2 = 0.005") --> (;x) do - x = V2_Nsram_outside_EdgeSeal.ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil) - x.drc(if_any( - !rectangles, - primary-secondary(Metal2_outside_EdgeSeal), - (if_any(enclosed(Metal2_outside_EdgeSeal) < 0.005.um, enclosed(Metal2_outside_EdgeSeal, projection, whole_edges, one_side_allowed, two_opposite_sides_allowed) < 0.05.um)))) -end.().output("V2.c1", "Min. Metal2 endcap enclosure of Via2 (For Via2 at Metal2 corners at least one side must be treated as an endcap and for the other sides rule V2.c can be applied.) = 0.05") --> do - Via3_edgC1_out.ext_outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true) -end.().output("V3.a", "Min. and max. Via3 width = 0.19") --> do - Via3_edgC1_out.ext_space(0.22.um, consider_intersecting_edges: false) -end.().output("V3.b", "Min. Via3 space = 0.22") --> (;via3NoES, x1, via3Array, via3In, via3BigArray, via3SepErr_1, via3SepErr_2) do - via3NoES = Via3_edgC1_out.dup - x1 = via3NoES.sized((0.29*0.5).um, acute_limit).sized(-(0.29*0.5).um, acute_limit) - via3Array = x1.sized(-(((4*0.19+3*0.22)-0.05)*0.5).um, acute_limit).sized((((4*0.19+3*0.22)-0.05)*0.5).um, acute_limit) - via3In = via3NoES.inside(via3Array) - via3BigArray = via3In.sized(0.143.um, acute_limit).sized(-0.143.um, acute_limit) - via3SepErr_1 = via3BigArray.ext_not(via3In) - via3SepErr_2 = via3SepErr_1.ext_not(via3SepErr_1.ext_rectangles) - via3SepErr_2.ext_or(via3In.ext_touching(via3SepErr_2)) -end.().output("V3.b1", "Min. Via3 space in an array of more than 3 rows and more then 3 columns (V3.b1 is only required in one direction. The distance of the other direction must be at least V3.b.) = 0.29") --> do - Via3.ext_enclosed(Metal3_Nslit, 0.005.um, outside_edges_are_errors: true) -end.().output("V3.c", "Min. Metal3 enclosure of Via3 = 0.005") --> (;x) do - x = V3_Nsram_outside_EdgeSeal.ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil) - x.drc(if_any( - !rectangles, - primary-secondary(Metal3_outside_EdgeSeal), - (if_any(enclosed(Metal3_outside_EdgeSeal) < 0.005.um, enclosed(Metal3_outside_EdgeSeal, projection, whole_edges, one_side_allowed, two_opposite_sides_allowed) < 0.05.um)))) -end.().output("V3.c1", "Min. Metal3 endcap enclosure of Via3 (For Via3 at Metal3 corners at least one side must be treated as an endcap and for the other sides rule V3.c can be applied.) = 0.05") --> do - Via4_edgC1_out.ext_outside(transG2L).ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil, inverted: true) -end.().output("V4.a", "Min. and max. Via4 width = 0.19") --> do - Via4_edgC1_out.ext_space(0.22.um, consider_intersecting_edges: false) -end.().output("V4.b", "Min. Via4 space = 0.22") --> (;via4NoES, x1, via4Array, via4In, via4BigArray, via4SepErr_1, via4SepErr_2) do - via4NoES = Via4_edgC1_out.dup - x1 = via4NoES.sized((0.29*0.5).um, acute_limit).sized(-(0.29*0.5).um, acute_limit) - via4Array = x1.sized(-(((4*0.19+3*0.22)-0.05)*0.5).um, acute_limit).sized((((4*0.19+3*0.22)-0.05)*0.5).um, acute_limit) - via4In = via4NoES.inside(via4Array) - via4BigArray = via4In.sized(0.143.um, acute_limit).sized(-0.143.um, acute_limit) - via4SepErr_1 = via4BigArray.ext_not(via4In) - via4SepErr_2 = via4SepErr_1.ext_not(via4SepErr_1.ext_rectangles) - via4SepErr_2.ext_or(via4In.ext_touching(via4SepErr_2)) -end.().output("V4.b1", "Min. Via4 space in an array of more than 3 rows and more then 3 columns (V4.b1 is only required in one direction. The distance of the other direction must be at least V4.b.) = 0.29") --> do - Via4.ext_enclosed(Metal4_Nslit, 0.005.um, outside_edges_are_errors: true) -end.().output("V4.c", "Min. Metal4 enclosure of Via4 = 0.005") --> (;x) do - x = V4_Nsram_outside_EdgeSeal.ext_rectangles(false, false, [["==", 0.19.um]], [["==", 0.19.um]], nil) - x.drc(if_any( - !rectangles, - primary-secondary(Metal4_outside_EdgeSeal), - (if_any(enclosed(Metal4_outside_EdgeSeal) < 0.005.um, enclosed(Metal4_outside_EdgeSeal, projection, whole_edges, one_side_allowed, two_opposite_sides_allowed) < 0.05.um)))) -end.().output("V4.c1", "Min. Metal4 endcap enclosure of Via4 (For Via4 at Metal4 corners at least one side must be treated as an endcap and for the other sides rule V4.c can be applied.) = 0.05") --> do - TopVia1_edgC1_out.ext_or(Vmim).ext_rectangles(false, false, [["==", 0.42.um]], [["==", 0.42.um]], nil, inverted: true) -end.().output("TV1.a", "Min. and max. TopVia1 width = 0.42") --> do - TopVia1_or_Vmim.ext_space(0.42.um) -end.().output("TV1.b", "Min. TopVia1 space = 0.42") --> do - TopVia1.ext_enclosed(Metal5_Nslit, 0.1.um, outside_edges_are_errors: true) -end.().output("TV1.c", "Min. Metal5 enclosure of TopVia1 = 0.1") --> do - TopVia1.ext_enclosed(TopMetal1_Nslit, 0.42.um, outside_edges_are_errors: true) -end.().output("TV1.d", "Min. TopMetal1 enclosure of TopVia1 = 0.42") --> do - TopMetal1_Nslit.ext_width(1.64.um) -end.().output("TM1.a", "Min. TopMetal1 width = 1.64") --> do - TopMetal1_Nslit.ext_space(1.64.um) -end.().output("TM1.b", "Min. TopMetal1 space or notch = 1.64") - -if $densityRules - -> do - TM1_density.ext_without_density(0.25 .. 0.7, 'll') - end.().output("TM1.c/d", "Global TopMetal1 density [%] = 25.00 .. 70.00") -end - - -if $fillerRules - -> do - TopMetal1_filler.ext_width(5.0.um) - end.().output("TM1Fil.a", "Min. TopMetal1:filler width = 5.00") - -> (;tmpX, tmpY) do - tmpX = TopMetal1_filler.sized(-(10.0*0.5).um, 0) - tmpY = TopMetal1_filler.sized(0, -(10.0*0.5).um) - TopMetal1_filler.ext_interacting(tmpX.ext_or(tmpY)).dup - end.().output("TM1Fil.a1", "Max. TopMetal1:filler width = 10.00") - -> do - TopMetal1_filler.ext_space(3.0.um) - end.().output("TM1Fil.b", "Min. TopMetal1:filler space = 3.00") - -> do - TopMetal1_filler.ext_separation(TopMetal1_Nslit, 3.0.um) - end.().output("TM1Fil.c", "Min. TopMetal1:filler space to TopMetal1 = 3.00") - -> do - TopMetal1_filler.ext_separation(TRANS, 4.9.um) - end.().output("TM1Fil.d", "Min. TopMetal1:filler space to TRANS = 4.90") -end - --> do - TopVia2_edgC1_out.ext_rectangles(false, false, [["==", 0.9.um]], [["==", 0.9.um]], nil, inverted: true) -end.().output("TV2.a", "Min. and max. TopVia2 width = 0.90") --> do - TopVia2.ext_space(1.06.um) -end.().output("TV2.b", "Min. TopVia2 space = 1.06") --> do - TopVia2.ext_enclosed(TopMetal1_Nslit, 0.5.um, outside_edges_are_errors: true) -end.().output("TV2.c", "Min. TopMetal1 enclosure of TopVia2 = 0.50") --> do - TopVia2.ext_enclosed(TopMetal2_Nslit, 0.5.um, outside_edges_are_errors: true) -end.().output("TV2.d", "Min. TopMetal2 enclosure of TopVia2 = 0.50") --> do - TopMetal2_Nslit.ext_width(2.0.um) -end.().output("TM2.a", "Min. TopMetal2 width = 2.00") --> do - TopMetal2_Nslit.ext_space(2.0.um) -end.().output("TM2.b", "Min. TopMetal2 space or notch = 2.00") - -if $recommendedRules - -> (;wide_TMetal2, l1) do - wide_TMetal2 = TopMetal2_outside_EdgeSeal.sized(-5.0.um/2.0, acute_limit).sized(5.0.um/2.0, acute_limit) - l1 = TopMetal2_outside_EdgeSeal.ext_separation(wide_TMetal2, 5.0.um, metric: projection, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - l1.ext_encloses_box(50.001.um, 0.001.um) - end.().output("TM2.bR", "Min. space of TopMetal2 lines if, at least one line is wider than 5.0 µm and the parallel run is more than 50.0 µm (Not checked within IND regions.) = 5.00") -end - - -if $densityRules - -> do - TM2_density.ext_without_density(0.25 .. 0.7, 'll') - end.().output("TM2.c/d", "Global TopMetal2 density [%] = 25.00 .. 70.00") -end - - -if $fillerRules - -> do - TopMetal2_filler.ext_width(5.0.um) - end.().output("TM2Fil.a", "Min. TopMetal2:filler width = 5.00") - -> (;tmpX, tmpY) do - tmpX = TopMetal2_filler.sized(-(10.0*0.5).um, 0) - tmpY = TopMetal2_filler.sized(0, -(10.0*0.5).um) - TopMetal2_filler.ext_interacting(tmpX.ext_or(tmpY)).dup - end.().output("TM2Fil.a1", "Max. TopMetal2:filler width = 10.00") - -> do - TopMetal2_filler.ext_space(3.0.um) - end.().output("TM2Fil.b", "Min. TopMetal2:filler space = 3.00") - -> do - TopMetal2_filler.ext_separation(TopMetal2_Nslit, 3.0.um) - end.().output("TM2Fil.c", "Min. TopMetal2:filler space to TopMetal2 = 3.00") - -> do - TopMetal2_filler.ext_separation(TRANS, 4.9.um) - end.().output("TM2Fil.d", "Min. TopMetal2:filler space to TRANS = 4.90") -end - --> do - Passiv.ext_width(2.1.um) -end.().output("Pas.a", "Min. Passiv width = 2.10") --> do - Passiv.ext_space(3.5.um) -end.().output("Pas.b", "Min. Passiv space or notch = 3.50") --> do - PasInsideChip.ext_enclosed(TopMetal2, 2.1.um, outside_edges_are_errors: true) -end.().output("Pas.c", "Min. TopMetal2 enclosure of Passiv (Not checked outside of sealring (edge-seal-passive)) = 2.10") --> do - subst_tie_hole_w_npn.ext_interacting(TRANS, inverted: true) -end.().output("npnG2.b", "NPN Substrate-Tie must enclose TRANS") --> do - npnPActRing.ext_enclosed(subst_tie_npn, 0.2.um, consider_intersecting_edges: false) -end.().output("npnG2.c", "pSD enclosure of Activ inside NPN Substrate-Tie = 0.20") --> do - NAct.ext_separation(subst_tie_trans, 1.21.um) -end.().output("npnG2.d.N_Activ", "Min. unrelated N+Activ space to TRANS = 1.21") --> do - NWell.ext_separation(subst_tie_trans, 1.21.um) -end.().output("npnG2.d.NWell", "Min. unrelated NWell space to TRANS = 1.21") --> do - PWell_block.ext_separation(subst_tie_trans, 1.21.um) -end.().output("npnG2.d.PWell_block", "Min. unrelated PWell:block space to TRANS = 1.21") --> do - nBuLayGen_nBuLay.ext_separation(subst_tie_trans, 1.21.um) -end.().output("npnG2.d.nBuLay", "Min. unrelated nBuLay space to TRANS = 1.21") --> do - nSD_block.ext_outside(subst_tie_hole_w_npn).ext_separation(subst_tie_trans, 1.21.um) -end.().output("npnG2.d.nSD_block", "Min. unrelated nSD:block space to TRANS = 1.21") --> do - GP_Nsram.ext_separation(subst_tie_trans, 0.9.um) -end.().output("npnG2.d1", "Min. unrelated GatPoly space to TRANS = 0.90") --> do - SalBlock.ext_separation(subst_tie_trans, 0.9.um) -end.().output("npnG2.d2", "Min. unrelated SalBlock space to TRANS = 0.90") --> do - Cont.ext_separation(subst_tie_trans, 0.27.um) -end.().output("npnG2.e", "Min. unrelated Cont space to TRANS = 0.27") --> do - emit_npn13G2.ext_with_length([[">", 0.07.um], ["<", 0.9.um]]) -end.().output("npn13G2.a", "Min. and max. npn13G2 emitter length = 0.90") - -if $recommendedRules - -> do - transG2.ext_interacting(SUB, [[">", 4000]]) - end.().output("npn13G2.bR", "Max. recommended total number of npn13G2 emitters per chip = 4000") -end - --> do - emit_npn13G2L.ext_with_length([[">", 0.07.um], ["<", 1.0.um]]) -end.().output("npn13G2L.a", "Min. npn13G2L emitter length = 1.00") --> do - emit_npn13G2L.ext_with_length([[">", 2.5.um]]) -end.().output("npn13G2L.b", "Max. npn13G2L emitter length = 2.50") - -if $recommendedRules - -> do - transG2L.ext_interacting(SUB, [[">", 800]]) - end.().output("npn13G2L.cR", "Max. recommended total number of npn13G2L emitters per chip = 800") -end - --> do - emit_npn13G2V.ext_with_length([[">", 0.12.um], ["<", 1.0.um]]) -end.().output("npn13G2V.a", "Min. npn13G2V emitter length = 1.00") --> do - emit_npn13G2V.ext_with_length([[">", 5.0.um]]) -end.().output("npn13G2V.b", "Max. npn13G2V emitter length = 5.00") - -if $recommendedRules - -> do - transG2V.ext_interacting(SUB, [[">", 800]]) - end.().output("npn13G2V.cR", "Max. recommended total number of npn13G2V emitters per chip = 800") -end - --> do - Rsil_all.ext_width(0.5.um) -end.().output("Rsil.a", "Min. GatPoly width = 0.50") --> do - RES.ext_separation(Cont, 0.12.um) -end.().output("Rsil.b", "Min. RES space to Cont = 0.12") --> (;x) do - x = rsil_gatpoly.ext_enclosed(RES, 1.0.um, consider_intersecting_edges: false, consider_touch_points: false, polygon_output: true) - x.ext_outside(Cont) -end.().output("Rsil.c", "Min. RES extension over GatPoly = 0.00") --> do - GP_Rsil_extended_external_pSD.dup -end.().output("Rsil.d", "Min. pSD space to GatPoly = 0.18") --> do - GP_Rsil_extended.ext_enclosed(EXTBlock, 0.18.um) -end.().output("Rsil.e", "Min. EXTBlock enclosure of GatPoly = 0.18") --> do - RES.ext_width(0.5.um) -end.().output("Rsil.f", "Min. RES length = 0.50") --> do - Rppd_all.ext_width(0.5.um) -end.().output("Rppd.a", "Min. GatPoly width = 0.50") --> do - Rppd_all_enclosure_pSD.dup -end.().output("Rppd.b", "Min. pSD enclosure of GatPoly = 0.18") --> (;x) do - x = SalBlock_Rppd.ext_extended(0.2.um, 0.2.um) - [ Rppd_Cont.ext_separation(SalBlock_Rppd, 0.2.um), - Rppd_Cont.ext_interacting(x, inverted: true) - ].each { |result| result.output("Rppd.c", "Min. and max. SalBlock space to Cont = 0.20") } -end.() --> do - GP_Rppd_extended.ext_enclosed(EXTBlock, 0.18.um, consider_intersecting_edges: false, consider_touch_points: false) -end.().output("Rppd.d", "Min. EXTBlock enclosure of GatPoly = 0.18") --> do - SalBlock_Rppd.ext_width(0.5.um) -end.().output("Rppd.e", "Min. SalBlock length = 0.50") --> do - Rhigh_a.ext_width(0.5.um) -end.().output("Rhi.a", "Min. GatPoly width = 0.50") --> do - Rhigh_identical_nsd_psd.dup -end.().output("Rhi.b", "pSD and nSD are identical (nSD:drawing is only permitted within Rhigh resistors. Apart from that, nSD is generated automatically (see section 4.2).)") --> do - GP_Rhigh_extended.ext_enclosed(pSD_nSD, 0.18.um) -end.().output("Rhi.c", "Min. pSD and nSD enclosure of GatPoly = 0.18") --> (;x) do - x = SalBlock_Rhigh.ext_extended(0.2.um, 0.2.um) - [ Rhigh_Cont.ext_separation(SalBlock_Rhigh, 0.2.um), - Rhigh_Cont.ext_interacting(x, inverted: true) - ].each { |result| result.output("Rhi.d", "Min. and max. SalBlock space to Cont = 0.20") } -end.() --> do - GP_Rhigh_extended.ext_enclosed(EXTBlock, 0.18.um, consider_intersecting_edges: false, consider_touch_points: false) -end.().output("Rhi.e", "Min. EXTBlock enclosure of GatPoly = 0.18") --> do - SalBlock_Rhigh.ext_width(0.5.um) -end.().output("Rhi.f", "Min. SalBlock length = 0.50") --> do - Iso_PWell_Act.ext_outside(schottky_nbl1).ext_enclosed(nBuLay, 1.24.um) -end.().output("nmosi.b", "Min. nBuLay enclosure of Iso-PWell-Activ (Iso-PWell-Activ = Activ AND nBuLay AND PWell) = 1.24") --> do - Iso_PWell_Act.ext_not(scr1_or_schottky_nbl1).ext_separation(NWell.with_holes, 0.39.um, max_angle: 180) -end.().output("nmosi.c", "Min. NWell space to Iso-PWell-Activ = 0.39") --> do - NWell_nBuLay.ext_width(0.62.um) -end.().output("nmosi.d", "Min. NWell-nBuLay width forming an unbroken ring around any Iso-PWell-Activ (NWell-nBuLay = NWell AND nBuLay) = 0.62") --> do - nSDBlock_Iso_PWell_Act.ext_width(0.62.um) -end.().output("nmosi.f", "Min. nSD:block width to separate ptap in nmosi = 0.62") --> (;tmp, x1) do - tmp = SalBlock_Iso_PWell_Act.ext_not(scr1_or_schottky_nbl1) - x1 = nSDBlock_Iso_PWell_Act.ext_enclosed(tmp.ext_not(tmp.ext_covering(npnMPA)), 0.15.um, polygon_output: true) - x1.ext_and(Activ) -end.().output("nmosi.g", "Min. SalBlock overlap of nSD:block over Activ = 0.15") --> do - schottky_contbar.ext_enclosed(schottky_pwb, 0.25.um) -end.().output("Sdiod.a", "Min. and max. PWell:block enclosure of ContBar = 0.25") --> do - schottky_contbar.ext_enclosed(schottky_nSDBlock, 0.4.um) -end.().output("Sdiod.b", "Min. and max. nSD:block enclosure of ContBar = 0.40") --> do - schottky_contbar.ext_enclosed(schottky_salblock, 0.45.um) -end.().output("Sdiod.c", "Min. and max. SalBlock enclosure of ContBar = 0.45") - -if $recommendedRules - -> do - Passiv_dfpad.ext_width(30.0.um) - end.().output("Pad.aR", "Min. recommended Pad width = 30.00") -end - --> do - Passiv_Pad_a1.dup -end.().output("Pad.a1", "Max. Pad width = 150.00") - -if $recommendedRules - -> do - Passiv_dfpad.ext_space(8.4.um) - end.().output("Pad.bR", "Min. recommended Pad space = 8.40") -end - --> do - Passiv_dfpad.ext_separation(Act_EdgeSeal_not_HRACT, 7.5.um) -end.().output("Pad.d", "Min. Pad space to EdgeSeal = 7.50") - -if $recommendedRules - -> do - Passiv_dfpad.ext_separation(Act_EdgeSeal_not_HRACT, 25.0.um) - end.().output("Pad.dR", "Min. recommended Pad to EdgeSeal space (Distance of Pad opening to EdgeSeal strongly depends on bonding procedure. For flip chip bonding via solder bumps (see section 6.9.1) or copper pillars (see section 6.9.2) or manual bonding a bigger distance may be required. We strongly recommend 25 µm distance for wedge-wedge wire bonding.) = 25.00") - -> do - Passiv_dfpad.ext_separation(Act_Not_EdgeSeal, 11.2.um) - end.().output("Pad.d1R", "Min. recommended Pad to Activ (inside chip area) space = 11.20") - -> do - TopVia2.ext_enclosed(belowTopMetaln_dfpad, 1.4.um) - end.().output("Pad.gR", "Min. recommended TopMetal1 (within dfpad) enclosure of TopVia2 = 1.40") - -> do - [ MIM.ext_and(Passiv_dfpad), - Gate.ext_and(Passiv_dfpad) - ].each { |result| result.output("Pad.jR", "No devices under Pad allowed (Components under pads can be damaged by mechanical stress.)") } - end.() - -> do - TopVia2.inside(Passiv_dfpad) - end.().output("Pad.kR", "TopVia2 under Pad not allowed (TopVia2 may be damaged during packaging process, we recommend not to use them below Passiv.)") -end - --> do - cupPad_candidat.ext_space(50.0.um, polygon_output: true) -end.().output("Padc.b", "Min. CuPillarPad space = Table 6.1") --> do - cupPad_candidat.ext_separation(Act_EdgeSeal_not_HRACT, 30.0.um, consider_touch_points: false, polygon_output: true) -end.().output("Padc.d", "Min. CuPillarPad space to EdgeSeal = 30.00") --> do - Activ_edgA1_in.ext_width(3.5.um, metric: projection) -end.().output("Seal.a_Activ", "Min. EdgeSeal-Activ width = 3.50") --> do - pSD_edgA1_in.ext_width(3.5.um, metric: projection) -end.().output("Seal.a_pSD", "Min. EdgeSeal-pSD width = 3.50") --> do - Metal1_edgA1_in.ext_width(3.5.um, metric: projection) -end.().output("Seal.a_Metal1", "Min. EdgeSeal-Metal1 width = 3.50") --> do - Metal2_edgA1_in.ext_width(3.5.um, metric: projection) -end.().output("Seal.a_Metal2", "Min. EdgeSeal-Metal2 width = 3.50") --> do - Metal3_edgA1_in.ext_width(3.5.um, metric: projection) -end.().output("Seal.a_Metal3", "Min. EdgeSeal-Metal3 width = 3.50") --> do - Metal4_edgA1_in.ext_width(3.5.um, metric: projection) -end.().output("Seal.a_Metal4", "Min. EdgeSeal-Metal4 width = 3.50") --> do - Metal5_edgA1_in.ext_width(3.5.um, metric: projection) -end.().output("Seal.a_Metal5", "Min. EdgeSeal-Metal5 width = 3.50") --> do - TopMetal1_edgA1_in.ext_width(3.5.um, metric: projection) -end.().output("Seal.a_TopMetal1", "Min. EdgeSeal-TopMetal1 width = 3.50") --> do - TopMetal2_edgA1_in.ext_width(3.5.um, metric: projection) -end.().output("Seal.a_TopMetal2", "Min. EdgeSeal-TopMetal2 width = 3.50") --> do - [ Cont_edgC1_in.ext_width(0.16.um), - Cont_edgC1_in.sized(-0.16.um/2.0, acute_limit).sized(0.16.um/2.0, acute_limit) - ].each { |result| result.output("Seal.c", "EdgeSeal-Cont ring width = 0.16") } -end.() --> do - [ Via1_edgC1_in.ext_width(0.19.um), - Via1_edgC1_in.sized(-0.19.um/2.0, acute_limit).sized(0.19.um/2.0, acute_limit) - ].each { |result| result.output("Seal.c1.Via1", "EdgeSeal-Via1 ring width = 0.19") } -end.() --> do - [ Via2_edgC1_in.ext_width(0.19.um), - Via2_edgC1_in.sized(-0.19.um/2.0, acute_limit).sized(0.19.um/2.0, acute_limit) - ].each { |result| result.output("Seal.c1.Via2", "EdgeSeal-Via2 ring width = 0.19") } -end.() --> do - [ Via3_edgC1_in.ext_width(0.19.um), - Via3_edgC1_in.sized(-0.19.um/2.0, acute_limit).sized(0.19.um/2.0, acute_limit) - ].each { |result| result.output("Seal.c1.Via3", "EdgeSeal-Via3 ring width = 0.19") } -end.() --> do - [ Via4_edgC1_in.ext_width(0.19.um), - Via4_edgC1_in.sized(-0.19.um/2.0, acute_limit).sized(0.19.um/2.0, acute_limit) - ].each { |result| result.output("Seal.c1.Via4", "EdgeSeal-Via4 ring width = 0.19") } -end.() --> do - [ TopVia1_edgC1_in.ext_width(0.42.um), - TopVia1_edgC1_in.sized(-0.42.um/2.0, acute_limit).sized(0.42.um/2.0, acute_limit) - ].each { |result| result.output("Seal.c2", "EdgeSeal-TopVia1 ring width = 0.42") } -end.() --> do - [ TopVia2_edgC1_in.ext_width(0.9.um), - TopVia2_edgC1_in.sized(-0.9.um/2.0, acute_limit).sized(0.9.um/2.0, acute_limit) - ].each { |result| result.output("Seal.c3", "EdgeSeal-TopVia2 ring width = 0.90") } -end.() --> do - Act_EdgeSeal_Cont_edgC1_in_enc.dup -end.().output("Seal.d.Cont", "Min. EdgeSeal-Activ enclosure of EdgeSeal-Cont ring = 1.30") --> do - Act_EdgeSeal_Via1_edgC1_in_enc.dup -end.().output("Seal.d.Via1", "Min. EdgeSeal-Activ enclosure of EdgeSeal-Via1 ring = 1.30") --> do - Act_EdgeSeal_Via2_edgC1_in_enc.dup -end.().output("Seal.d.Via2", "Min. EdgeSeal-Activ enclosure of EdgeSeal-Via2 ring = 1.30") --> do - Act_EdgeSeal_Via3_edgC1_in_enc.dup -end.().output("Seal.d.Via3", "Min. EdgeSeal-Activ enclosure of EdgeSeal-Via3 ring = 1.30") --> do - Act_EdgeSeal_Via4_edgC1_in_enc.dup -end.().output("Seal.d.Via4", "Min. EdgeSeal-Activ enclosure of EdgeSeal-Via4 ring = 1.30") --> do - Act_EdgeSeal_TopVia1_edgC1_in_enc.dup -end.().output("Seal.d.TopVia1", "Min. EdgeSeal-Activ enclosure of EdgeSeal-TopVia1 ring = 1.30") --> do - Act_EdgeSeal_TopVia2_edgC1_in_enc.dup -end.().output("Seal.d.TopVia2", "Min. EdgeSeal-Activ enclosure of EdgeSeal-TopVia2 ring = 1.30") --> do - ring_passiv.ext_not(ring_passiv.sized(-4.2.um/2.0+1.dbu, acute_limit).sized(4.2.um/2.0-1.dbu, acute_limit)) -end.().output("Seal.e", "Min. Passiv ring width outside of sealring = 4.20") --> do - ring_passiv_Activ_edgA1_in_Seal_f_Activ_sep.dup -end.().output("Seal.f.Activ", "Min. Passiv ring outside of sealring space to EdgeSeal-Activ = 1.00") --> do - ring_passiv_pSD_edgA1_in_Seal_f_pSD_sep.dup -end.().output("Seal.f.pSD", "Min. Passiv ring outside of sealring space to EdgeSeal-pSD = 1.00") --> do - ring_passiv_Metal1_edgA1_in_Seal_f_Metal1_sep.dup -end.().output("Seal.f.Metal1", "Min. Passiv ring outside of sealring space to EdgeSeal-Metal1 = 1.00") --> do - ring_passiv_Metal2_edgA1_in_Seal_f_Metal2_sep.dup -end.().output("Seal.f.Metal2", "Min. Passiv ring outside of sealring space to EdgeSeal-Metal2 = 1.00") --> do - ring_passiv_Metal3_edgA1_in_Seal_f_Metal3_sep.dup -end.().output("Seal.f.Metal3", "Min. Passiv ring outside of sealring space to EdgeSeal-Metal3 = 1.00") --> do - ring_passiv_Metal4_edgA1_in_Seal_f_Metal4_sep.dup -end.().output("Seal.f.Metal4", "Min. Passiv ring outside of sealring space to EdgeSeal-Metal4 = 1.00") --> do - ring_passiv_Metal5_edgA1_in_Seal_f_Metal5_sep.dup -end.().output("Seal.f.Metal5", "Min. Passiv ring outside of sealring space to EdgeSeal-Metal5 = 1.00") --> do - ring_passiv_TopMetal1_edgA1_in_Seal_f_TopMetal1_sep.dup -end.().output("Seal.f.TopMetal1", "Min. Passiv ring outside of sealring space to EdgeSeal-TopMetal1 = 1.00") --> do - ring_passiv_TopMetal2_edgA1_in_Seal_f_TopMetal2_sep.dup -end.().output("Seal.f.TopMetal2", "Min. Passiv ring outside of sealring space to EdgeSeal-TopMetal2 = 1.00") --> do - EdgeSeal.ext_with_angle([["==", 45]]).ext_with_length([["<", 21.0.um]]) -end.().output("Seal.k", "Min. EdgeSeal 45-degree corner length = 21.00") --> do - SUB.ext_interacting(EdgeSeal, [[">", 1]]) -end.().output("Seal.m", "Only one sealring per chip allowed") --> do - MIM_Mim_a.dup -end.().output("MIM.a", "Min. MIM width = 1.14") --> do - MIM.ext_space(0.6.um) -end.().output("MIM.b", "Min. MIM space = 0.60") --> do - TopMetal1_Nslit.ext_separation(MIM, 0.6.um) -end.().output("MIM.e", "Min. TopMetal1 space to MIM = 0.60") --> do - MIM_Mim_f.dup -end.().output("MIM.f", "Min. MIM area per MIM device (µm²) = 1.30") --> do - MIM.ext_with_area([[">", 5625.0.um2]]) -end.().output("MIM.g", "Max. MIM area per MIM device (µm²) = 5625.00") --> do - MIM.ext_not(temp_layer_1) -end.().output("MIM.h", "TopVia1 must be over MIM") - -if $latchUpRules - -> (;x) do - x = all_ntie.ext_enlarge_inside(NWell, 20.0.um, 0.1.um) - PAct_NWell.ext_not(x).ext_outside(devExclud) - end.().output("LU.a", "Max. space from any portion of P+Activ inside NWell to an nSD-NWell tie = 20.00") - -> (;x) do - x = all_ptie.ext_enlarge_inside(PWell_drv, 20.0.um, 0.1.um) - NAct_PWell.ext_not(x).ext_outside(devExclud) - end.().output("LU.b", "Max. space from any portion of N+Activ inside PWell to an pSD-PWell tie = 20.00") - -> (;sizedA, drcErrA, drcErrA_Edge, drcErrA_Poly) do - sizedA = Abut_NWell_Tie_Cont.ext_enlarge_inside(Act_connect.ext_interacting(Gate), 6.um, 0.21.um).ext_interacting(Cont_not_outside_NAct, inverted: true) - drcErrA = Abut_NWell_Tie.ext_not(sizedA) - drcErrA_Edge = drcErrA.ext_coincident_part(sizedA, outside: true) - drcErrA_Poly = drcErrA.ext_with_coincident_edges(drcErrA_Edge) - drcErrA_Poly.ext_interacting(Cont_not_outside_NAct, inverted: true) - end.().output("LU.c", "Max. extension of an abutted NWell tie beyond Cont = 6.00") - -> (;sizedA, drcErrA, drcErrA_Edge, drcErrA_Poly) do - sizedA = Abut_PWell_Tie_Cont.ext_enlarge_inside(Act_connect, 6.um, 0.21.um).ext_interacting(Cont_not_outside_PAct, inverted: true) - drcErrA = Abut_PWell_Tie.ext_not(sizedA) - drcErrA_Edge = drcErrA.ext_coincident_part(sizedA, outside: true) - drcErrA_Poly = drcErrA.ext_with_coincident_edges(drcErrA_Edge) - drcErrA_Poly.ext_interacting(Cont_not_outside_PAct, inverted: true) - end.().output("LU.c1", "Max. extension of an abutted substrate tie beyond Cont = 6.00") - -> (;sizedA, tmp, drcErrA, drcErrA_Edge) do - sizedA = size_Cont.dup - tmp = NAct_NWell.ext_outside(scr1).ext_interacting(Activ.ext_interacting(GatPoly), inverted: true) - drcErrA = tmp.ext_not(sizedA) - drcErrA_Edge = drcErrA.ext_coincident_part(sizedA, outside: true) - drcErrA.ext_with_coincident_edges(drcErrA_Edge) - end.().output("LU.d", "Max. extension of NWell tie Activ tie beyond Cont = 6.00") - -> (;sizedA, drcErrA, drcErrA_Edge) do - sizedA = Cont.ext_enlarge_inside(Act_connect, 6.um, 0.21.um) - drcErrA = PWell_Tie_wo_varicap_abut.ext_not(sizedA).ext_not(GatPoly) - drcErrA_Edge = drcErrA.ext_coincident_part(sizedA, outside: true) - drcErrA.ext_with_coincident_edges(drcErrA_Edge) - end.().output("LU.d1", "Max. extension of an substrate tie Activ beyond Cont = 6.00") -end - --> do - Metal1_slit_not_pad.ext_width(2.8.um) -end.().output("Slt.a.M1", "Min. Metal1:slit width = 2.80") --> (;tmp) do - tmp = Metal1_slit_not_pad.ext_with_length([[">", 20.0.um]]) - Metal1_slit.ext_with_coincident_edges(tmp) -end.().output("Slt.b.M1", "Max. Metal1:slit width = 20.00") --> (;m1mitSlots, m1_L1, m1_L2) do - m1mitSlots = sltc_M1.ext_not(Metal1_slit) - m1_L1 = m1mitSlots.sized(-3.um, acute_limit) - m1_L2 = m1_L1.sized(-12.um, acute_limit) - m1_L2.sized(15.um, acute_limit) -end.().output("Slt.c.M1", "Max. Metal1 width without requiring a slit = 30.00") --> do - Metal1_slit.ext_and(pad) -end.().output("Slt.e.M1", "No slits required on pads") --> do - Metal1_slit_not_pad.ext_enclosed(Metal1, 1.0.um) -end.().output("Slt.f.M1", "Min. Metal1 enclosure of Metal1:slit = 1.00") --> do - [ Metal1_slit_not_pad.ext_separation(Cont, 0.3.um), - Metal1_slit_not_pad.ext_separation(Via1, 0.3.um) - ].each { |result| result.output("Slt.h1", "Min. Metal1:slit space to Cont and Via1 = 0.30") } -end.() --> do - Metal2_slit_not_pad.ext_width(2.8.um) -end.().output("Slt.a.M2", "Min. Metal2:slit width = 2.80") --> (;tmp) do - tmp = Metal2_slit_not_pad.ext_with_length([[">", 20.0.um]]) - Metal2_slit.ext_with_coincident_edges(tmp) -end.().output("Slt.b.M2", "Max. Metal2:slit width = 20.00") --> (;m2mitSlots, m2_L1, m2_L2) do - m2mitSlots = sltc_M2.ext_not(Metal2_slit) - m2_L1 = m2mitSlots.sized(-3.um, acute_limit) - m2_L2 = m2_L1.sized(-12.um, acute_limit) - m2_L2.sized(15.um, acute_limit) -end.().output("Slt.c.M2", "Max. Metal2 width without requiring a slit = 30.00") --> do - Metal2_slit.ext_and(pad) -end.().output("Slt.e.M2", "No slits required on pads") --> do - Metal2_slit_not_pad.ext_enclosed(Metal2, 1.0.um) -end.().output("Slt.f.M2", "Min. Metal2 enclosure of Metal2:slit = 1.00") --> do - [ Metal2_slit_not_pad.ext_separation(Via1, 0.3.um), - Metal2_slit_not_pad.ext_separation(Via2, 0.3.um) - ].each { |result| result.output("Slt.h2.M2", "Min. Metal2:slit space to Via1 and Via2 = 0.30") } -end.() --> do - Metal3_slit_not_pad.ext_width(2.8.um) -end.().output("Slt.a.M3", "Min. Metal3:slit width = 2.80") --> (;tmp) do - tmp = Metal3_slit_not_pad.ext_with_length([[">", 20.0.um]]) - Metal3_slit.ext_with_coincident_edges(tmp) -end.().output("Slt.b.M3", "Max. Metal3:slit width = 20.00") --> (;m3mitSlots, m3_L1, m3_L2) do - m3mitSlots = sltc_M3.ext_not(Metal3_slit) - m3_L1 = m3mitSlots.sized(-3.um, acute_limit) - m3_L2 = m3_L1.sized(-12.um, acute_limit) - m3_L2.sized(15.um, acute_limit) -end.().output("Slt.c.M3", "Max. Metal3 width without requiring a slit = 30.00") --> do - Metal3_slit.ext_and(pad) -end.().output("Slt.e.M3", "No slits required on pads") --> do - Metal3_slit_not_pad.ext_enclosed(Metal3, 1.0.um) -end.().output("Slt.f.M3", "Min. Metal3 enclosure of Metal2:slit = 1.00") --> do - [ Metal3_slit_not_pad.ext_separation(Via2, 0.3.um), - Metal3_slit_not_pad.ext_separation(Via3, 0.3.um) - ].each { |result| result.output("Slt.h2.M3", "Min. Metal3:slit space to Via2 and Via3 = 0.30") } -end.() --> do - Metal4_slit_not_pad.ext_width(2.8.um) -end.().output("Slt.a.M4", "Min. Metal4:slit width = 2.80") --> (;tmp) do - tmp = Metal4_slit_not_pad.ext_with_length([[">", 20.0.um]]) - Metal4_slit.ext_with_coincident_edges(tmp) -end.().output("Slt.b.M4", "Max. Metal4:slit width = 20.00") --> (;m4mitSlots, m4_L1, m4_L2) do - m4mitSlots = sltc_M4.ext_not(Metal4_slit) - m4_L1 = m4mitSlots.sized(-3.um, acute_limit) - m4_L2 = m4_L1.sized(-12.um, acute_limit) - m4_L2.sized(15.um, acute_limit) -end.().output("Slt.c.M4", "Max. Metal4 width without requiring a slit = 30.00") --> do - Metal4_slit.ext_and(pad) -end.().output("Slt.e.M4", "No slits required on pads") --> do - Metal4_slit_not_pad.ext_enclosed(Metal4, 1.0.um) -end.().output("Slt.f.M4", "Min. Metal4 enclosure of Metal4:slit = 1.00") --> do - [ Metal4_slit_not_pad.ext_separation(Via3, 0.3.um), - Metal4_slit_not_pad.ext_separation(Via4, 0.3.um) - ].each { |result| result.output("Slt.h2.M4", "Min. Metal4:slit space to Via3 and Via4 = 0.30") } -end.() --> do - Metal5_slit_not_pad.ext_width(2.8.um) -end.().output("Slt.a.M5", "Min. Metal5:slit width = 2.80") --> (;tmp) do - tmp = Metal5_slit_not_pad.ext_with_length([[">", 20.0.um]]) - Metal5_slit.ext_with_coincident_edges(tmp) -end.().output("Slt.b.M5", "Max. Metal5:slit width = 20.00") --> (;m5mitSlots, m5_L1, m5_L2) do - m5mitSlots = sltc_M5.ext_not(Metal5_slit) - m5_L1 = m5mitSlots.sized(-3.um, acute_limit) - m5_L2 = m5_L1.sized(-12.um, acute_limit) - m5_L2.sized(15.um, acute_limit) -end.().output("Slt.c.M5", "Max. Metal5 width without requiring a slit = 30.00") --> do - Metal5_slit.ext_and(pad) -end.().output("Slt.e.M5", "No slits required on pads") --> do - Metal5_slit_not_pad.ext_enclosed(Metal5, 1.0.um) -end.().output("Slt.f.M5", "Min. Metal5 enclosure of Metal5:slit = 1.00") --> do - Metal5_slit_MIM_Slt_g_M5_sep.dup -end.().output("Slt.g.M5", "Min. Metal5:slit and TopMetal1:slit space to MIM = 0.60") --> do - [ Metal5_slit_not_pad.ext_separation(Via4, 0.3.um), - Metal5_slit_not_pad.ext_separation(TopVia1, 0.3.um) - ].each { |result| result.output("Slt.h2.M5", "Min. Metal5:slit space to Via4 and Via5 = 0.30") } -end.() --> do - TopMetal1_slit_not_pad.ext_width(2.8.um) -end.().output("Slt.a.TM1", "Min. TopMetal1:slit width = 2.80") --> (;tmp) do - tmp = TopMetal1_slit_not_pad.ext_with_length([[">", 20.0.um]]) - TopMetal1_slit.ext_with_coincident_edges(tmp) -end.().output("Slt.b.TM1", "Max. TopMetal1:slit width = 20.00") --> (;tM1mitSlots, tM1_L1, tM1_L2) do - tM1mitSlots = sltc_TM1.ext_not(TopMetal1_slit) - tM1_L1 = tM1mitSlots.sized(-3.um, acute_limit) - tM1_L2 = tM1_L1.sized(-12.um, acute_limit) - tM1_L2.sized(15.um, acute_limit) -end.().output("Slt.c.TM1", "Max. TopMetal1 width without requiring a slit = 30.00") --> do - TopMetal1_slit.ext_and(pad) -end.().output("Slt.e.TM1", "No slits required on pads") --> do - TopMetal1_slit_not_pad.ext_enclosed(TopMetal1, 1.0.um) -end.().output("Slt.f.TM1", "Min. TopMetal1 enclosure of TopMetal1:slit = 1.00") --> do - TopMetal1_slit_MIM_Slt_g_TM1_sep.dup -end.().output("Slt.g.TM1", "Min. Metal5:slit and TopMetal1:slit space to MIM = 0.60") --> do - TopMetal1_slit_not_pad.ext_separation(TopVia1, 1.0.um) -end.().output("Slt.h3", "Min. TopMetal1:slit space to TopVia1 and TopVia2 = 1.00") --> do - TopMetal2_slit_not_pad.ext_width(2.8.um) -end.().output("Slt.a.TM2", "Min. TopMetal2:slit width = 2.80") --> (;tmp) do - tmp = TopMetal2_slit_not_pad.ext_with_length([[">", 20.0.um]]) - TopMetal2_slit.ext_with_coincident_edges(tmp) -end.().output("Slt.b.TM2", "Max. TopMetal2:slit width = 20.00") --> (;tM2mitSlots, tM2_L1, tM2_L2) do - tM2mitSlots = sltc_TM2.ext_not(TopMetal2_slit) - tM2_L1 = tM2mitSlots.sized(-3.um, acute_limit) - tM2_L2 = tM2_L1.sized(-12.um, acute_limit) - tM2_L2.sized(15.um, acute_limit) -end.().output("Slt.c.TM2", "Max. TopMetal2 width without requiring a slit = 30.00") --> do - TopMetal2_slit.ext_and(pad) -end.().output("Slt.e.TM2", "No slits required on pads") --> do - TopMetal2_slit_not_pad.ext_enclosed(TopMetal2, 1.0.um) -end.().output("Slt.f.TM2", "Min. TopMetal2 enclosure of TopMetal2:slit = 1.00") --> do - TopMetal2_slit_not_pad.ext_separation(TopVia2, 1.0.um) -end.().output("Slt.h4", "Min. TopMetal2:slit space to TopVia2 = 1.00") --> do - Activ_pin.ext_not(Activ) -end.().output("Pin.a", "Min. Activ enclosure of Activ:pin = 0.00") --> do - GatPoly_pin.ext_not(GatPoly) -end.().output("Pin.b", "Min. GatPoly enclosure of GatPoly:pin = 0.00") --> do - Metal1_pin.ext_not(Metal1) -end.().output("Pin.e", "Min. Metal1 enclosure of Metal1:pin = 0.00") --> do - Metal2_pin.ext_not(Metal2) -end.().output("Pin.f.M2", "Min. Metal2 enclosure of Metal2:pin = 0.00") --> do - Metal3_pin.ext_not(Metal3) -end.().output("Pin.f.M3", "Min. Metal3 enclosure of Metal3:pin = 0.00") --> do - Metal4_pin.ext_not(Metal4) -end.().output("Pin.f.M4", "Min. Metal4 enclosure of Metal4:pin = 0.00") --> do - Metal5_pin.ext_not(Metal5) -end.().output("Pin.f.M5", "Min. Metal5 enclosure of Metal5:pin = 0.00") --> do - TopMetal1_pin.ext_not(TopMetal1) -end.().output("Pin.g", "Min. TopMetal1 enclosure of TopMetal1:pin = 0.00") --> do - TopMetal2_pin.ext_not(TopMetal2) -end.().output("Pin.h", "Min. TopMetal2 enclosure of TopMetal2:pin = 0.00") --> do - PActHV_digi.ext_outside(SVaricap_or_schottky_nbl1).ext_enclosed(NWell, 0.31.um, consider_overlaps: true) -end.().output("NW.c1.dig", "Min. NWell enclosure of P+Activ inside ThickGateOx inside DigiBnd = 0.31") --> do - NWell.ext_separation(NActHV_digi, 0.31.um) -end.().output("NW.d1.dig", "Min. NWell space to external N+Activ inside ThickGateOx inside DigiBnd = 0.31") --> do - NAct_NWellHV_digi.ext_enclosed(NWell, 0.24.um, consider_overlaps: true) -end.().output("NW.e1.dig", "Min. NWell enclosure of NWell tie surrounded entirely by NWell in N+Activ inside ThickGateOx inside DigiBnd = 0.24") --> do - NWell.ext_separation(PAct_PWellHV_digi, 0.24.um) -end.().output("NW.f1.dig", "Min. NWell space to substrate tie in P+Activ inside ThickGateOx inside DigiBnd = 0.24") --> do - Cont_SQ.ext_not(SVaricap_or_trans_bip).ext_enclosed(Act_Nsram_or_Activ_mask.ext_and(DigiBnd), 0.05.um, consider_overlaps: true) -end.().output("Cnt.c.Digi", "Min. Activ enclosure of Cont inside DigiBnd = 0.05") --> do - LBE.ext_width(100.0.um) -end.().output("LBE.a", "Min. LBE width = 100.00") --> do - LBE.sized(-1500.0.um/2.0, acute_limit).sized(1500.0.um/2.0, acute_limit) -end.().output("LBE.b", "Max. LBE width = 1500.00") --> do - LBE.ext_with_area([[">", 250000.0.um2]]) -end.().output("LBE.b1", "Max. LBE area (µm²) = 250000.00") --> do - LBE.ext_with_area([["<", 250000.0.um2]]) -end.().output("LBE.b2", "Min. LBE area (µm²) = 30000.00") --> do - LBE.ext_space(100.0.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true) -end.().output("LBE.c", "Min. LBE space or notch = 100.00") --> (;lbe_in_seal) do - lbe_in_seal = LBE.inside(EdgeSeal.holes.merge) - lbe_in_seal.ext_separation(EdgeSeal, 150.0.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true) -end.().output("LBE.d", "Min. LBE space to inner edge of EdgeSeal = 150.00") --> do - LBE.ext_separation(dfpad, 50.0.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true) -end.().output("LBE.e.dfPad", "Min. LBE space to dfpad and Passiv = 50.00") --> do - LBE.ext_separation(Passiv, 50.0.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true) -end.().output("LBE.e.Passiv", "Min. LBE space to dfpad and Passiv = 50.00") --> do - LBE.ext_separation(Activ, 30.0.um, consider_intersecting_edges: false, ignore_non_axis_aligned_edges: true) -end.().output("LBE.f", "Min. LBE space to Activ = 30.00") --> do - LBE.with_holes.dup -end.().output("LBE.h", "No LBE ring allowed") - -if $densityRules - -> do - LBE.ext_with_density(0.2 .. 1.0, 'll') - end.().output("LBE.i", "Max. global LBE density [%] = 20.00") -end - --> do - BiWind.dup -end.().output("forbidden.BiWind", "Forbidden drawn layer BiWind on GDS layer 3/0 = 3/0") --> do - PEmWind.dup -end.().output("forbidden.PEmWind", "Forbidden drawn layer PEmWind on GDS layer 11/0 = 11/0") --> do - BasPoly.dup -end.().output("forbidden.BasPoly", "Forbidden drawn layer BasPoly on GDS layer 13/0 = 13/0") --> do - DeepCo.dup -end.().output("forbidden.DeepCo", "Forbidden drawn layer DeepCo on GDS layer 35/0 = 35/0") --> do - PEmPoly.dup -end.().output("forbidden.PEmPoly", "Forbidden drawn layer PEmPoly on GDS layer 53/0 = 53/0") --> do - EmPoly.dup -end.().output("forbidden.EmPoly", "Forbidden gen./drawn layer EmPoly on GDS layer 53/0 = 53/0") --> do - LDMOS.dup -end.().output("forbidden.LDMOS", "Forbidden drawn layer LDMOS on GDS layer 57/0 = 57/0") --> do - PBiWind.dup -end.().output("forbidden.PBiWind", "Forbidden drawn layer PBiWind on GDS layer 58/0 = 58/0") --> do - Flash.dup -end.().output("forbidden.Flash", "Forbidden drawn layer Flash on GDS layer 71/0 = 71/0") --> do - ColWind.dup -end.().output("forbidden.ColWind", "Forbidden drawn layer ColWind on GDS layer 139/0 = 139/0") - -if $offGridRules - -> do - NWell.ongrid(5.nm) - end.().output("OffGrid.NWell", "NWell is off-grid") - -> do - PWell.ongrid(5.nm) - end.().output("OffGrid.PWell", "PWell is off-grid") - -> do - PWell_block.ongrid(5.nm) - end.().output("OffGrid.PWell_block", "PWell_block is off-grid") - -> do - nBuLay.ongrid(5.nm) - end.().output("OffGrid.nBuLay", "nBuLay is off-grid") - -> do - nBuLay_block.ongrid(5.nm) - end.().output("OffGrid.nBuLay_block", "nBuLay_block is off-grid") - -> do - Activ.ongrid(5.nm) - end.().output("OffGrid.Activ", "Activ is off-grid") - -> do - ThickGateOx.ongrid(5.nm) - end.().output("OffGrid.ThickGateOx", "ThickGateOx is off-grid") - -> do - Activ_filler.ongrid(5.nm) - end.().output("OffGrid.Activ_filler", "Activ_filler is off-grid") - -> do - GatPoly_filler.ongrid(5.nm) - end.().output("OffGrid.GatPoly_filler", "GatPoly_filler is off-grid") - -> do - GatPoly.ongrid(5.nm) - end.().output("OffGrid.GatPoly", "GatPoly is off-grid") - -> do - pSD.ongrid(5.nm) - end.().output("OffGrid.pSD", "pSD is off-grid") - -> do - nSD.ongrid(5.nm) - end.().output("OffGrid.nSD", "nSD is off-grid") - -> do - nSD_block.ongrid(5.nm) - end.().output("OffGrid.nSD_block", "nSD_block is off-grid") - -> do - EXTBlock.ongrid(5.nm) - end.().output("OffGrid.EXTBlock", "EXTBlock is off-grid") - -> do - SalBlock.ongrid(5.nm) - end.().output("OffGrid.SalBlock", "SalBlock is off-grid") - -> do - Cont.ongrid(5.nm) - end.().output("OffGrid.Cont", "Cont is off-grid") - -> do - Activ_nofill.ongrid(5.nm) - end.().output("OffGrid.Activ_nofill", "Activ_nofill is off-grid") - -> do - GatPoly_nofill.ongrid(5.nm) - end.().output("OffGrid.GatPoly_nofill", "GatPoly_nofill is off-grid") - -> do - Metal1.ongrid(5.nm) - end.().output("OffGrid.Metal1", "Metal1 is off-grid") - -> do - Via1.ongrid(5.nm) - end.().output("OffGrid.Via1", "Via1 is off-grid") - -> do - Metal2.ongrid(5.nm) - end.().output("OffGrid.Metal2", "Metal2 is off-grid") - -> do - Via2.ongrid(5.nm) - end.().output("OffGrid.Via2", "Via2 is off-grid") - -> do - Metal3.ongrid(5.nm) - end.().output("OffGrid.Metal3", "Metal3 is off-grid") - -> do - Via3.ongrid(5.nm) - end.().output("OffGrid.Via3", "Via3 is off-grid") - -> do - Metal4.ongrid(5.nm) - end.().output("OffGrid.Metal4", "Metal4 is off-grid") - -> do - Via4.ongrid(5.nm) - end.().output("OffGrid.Via4", "Via4 is off-grid") - -> do - Metal5.ongrid(5.nm) - end.().output("OffGrid.Metal5", "Metal5 is off-grid") - -> do - MIM.ongrid(5.nm) - end.().output("OffGrid.MIM", "MIM is off-grid") - -> do - Vmim.ongrid(5.nm) - end.().output("OffGrid.Vmim", "Vmim is off-grid") - -> do - TopVia1.ongrid(5.nm) - end.().output("OffGrid.TopVia1", "TopVia1 is off-grid") - -> do - TopMetal1.ongrid(5.nm) - end.().output("OffGrid.TopMetal1", "TopMetal1 is off-grid") - -> do - TopVia2.ongrid(5.nm) - end.().output("OffGrid.TopVia2", "TopVia2 is off-grid") - -> do - TopMetal2.ongrid(5.nm) - end.().output("OffGrid.TopMetal2", "TopMetal2 is off-grid") - -> do - Passiv.ongrid(5.nm) - end.().output("OffGrid.Passiv", "Passiv is off-grid") - -> do - Metal1_filler.ongrid(5.nm) - end.().output("OffGrid.Metal1_filler", "Metal1_filler is off-grid") - -> do - Metal2_filler.ongrid(5.nm) - end.().output("OffGrid.Metal2_filler", "Metal2_filler is off-grid") - -> do - Metal3_filler.ongrid(5.nm) - end.().output("OffGrid.Metal3_filler", "Metal3_filler is off-grid") - -> do - Metal4_filler.ongrid(5.nm) - end.().output("OffGrid.Metal4_filler", "Metal4_filler is off-grid") - -> do - Metal5_filler.ongrid(5.nm) - end.().output("OffGrid.Metal5_filler", "Metal5_filler is off-grid") - -> do - TopMetal1_filler.ongrid(5.nm) - end.().output("OffGrid.TopMetal1_filler", "TopMetal1_filler is off-grid") - -> do - TopMetal2_filler.ongrid(5.nm) - end.().output("OffGrid.TopMetal2_filler", "TopMetal2_filler is off-grid") - -> do - Metal1_nofill.ongrid(5.nm) - end.().output("OffGrid.Metal1_nofill", "Metal1_nofill is off-grid") - -> do - Metal2_nofill.ongrid(5.nm) - end.().output("OffGrid.Metal2_nofill", "Metal2_nofill is off-grid") - -> do - Metal3_nofill.ongrid(5.nm) - end.().output("OffGrid.Metal3_nofill", "Metal3_nofill is off-grid") - -> do - Metal4_nofill.ongrid(5.nm) - end.().output("OffGrid.Metal4_nofill", "Metal4_nofill is off-grid") - -> do - Metal5_nofill.ongrid(5.nm) - end.().output("OffGrid.Metal5_nofill", "Metal5_nofill is off-grid") - -> do - TopMetal1_nofill.ongrid(5.nm) - end.().output("OffGrid.TopMetal1_nofill", "TopMetal1_nofill is off-grid") - -> do - TopMetal2_nofill.ongrid(5.nm) - end.().output("OffGrid.TopMetal2_nofill", "TopMetal2_nofill is off-grid") - -> do - NoMetFiller.ongrid(5.nm) - end.().output("OffGrid.NoMetFiller", "NoMetFiller is off-grid") - -> do - Metal1_slit.ongrid(5.nm) - end.().output("OffGrid.Metal1_slit", "Metal1_slit is off-grid") - -> do - Metal2_slit.ongrid(5.nm) - end.().output("OffGrid.Metal2_slit", "Metal2_slit is off-grid") - -> do - Metal3_slit.ongrid(5.nm) - end.().output("OffGrid.Metal3_slit", "Metal3_slit is off-grid") - -> do - Metal4_slit.ongrid(5.nm) - end.().output("OffGrid.Metal4_slit", "Metal4_slit is off-grid") - -> do - Metal5_slit.ongrid(5.nm) - end.().output("OffGrid.Metal5_slit", "Metal5_slit is off-grid") - -> do - TopMetal1_slit.ongrid(5.nm) - end.().output("OffGrid.TopMetal1_slit", "TopMetal1_slit is off-grid") - -> do - TopMetal2_slit.ongrid(5.nm) - end.().output("OffGrid.TopMetal2_slit", "TopMetal2_slit is off-grid") - -> do - EdgeSeal.ongrid(5.nm) - end.().output("OffGrid.EdgeSeal", "EdgeSeal is off-grid") - -> do - EmWind.ongrid(5.nm) - end.().output("OffGrid.EmWind", "EmWind is off-grid") - -> do - dfpad.ongrid(5.nm) - end.().output("OffGrid.dfpad", "dfpad is off-grid") - -> do - Polimide.ongrid(5.nm) - end.().output("OffGrid.Polimide", "Polimide is off-grid") - -> do - TRANS.ongrid(5.nm) - end.().output("OffGrid.TRANS", "TRANS is off-grid") - -> do - IND.ongrid(5.nm) - end.().output("OffGrid.IND", "IND is off-grid") - -> do - RES.ongrid(5.nm) - end.().output("OffGrid.RES", "RES is off-grid") - -> do - RFMEM.ongrid(5.nm) - end.().output("OffGrid.RFMEM", "RFMEM is off-grid") - -> do - Recog_diode.ongrid(5.nm) - end.().output("OffGrid.Recog_diode", "Recog_diode is off-grid") - -> do - Recog_esd.ongrid(5.nm) - end.().output("OffGrid.Recog_esd", "Recog_esd is off-grid") - -> do - DigiBnd.ongrid(5.nm) - end.().output("OffGrid.DigiBnd", "DigiBnd is off-grid") - -> do - DigiSub.ongrid(5.nm) - end.().output("OffGrid.DigiSub", "DigiSub is off-grid") - -> do - SRAM.ongrid(5.nm) - end.().output("OffGrid.SRAM", "SRAM is off-grid") - -> do - dfpad_pillar.ongrid(5.nm) - end.().output("OffGrid.dfpad_pillar", "dfpad_pillar is off-grid") - -> do - dfpad_sbump.ongrid(5.nm) - end.().output("OffGrid.dfpad_sbump", "dfpad_sbump is off-grid") - -> do - DeepVia.ongrid(5.nm) - end.().output("OffGrid.DeepVia", "DeepVia is off-grid") - -> do - LBE.ongrid(5.nm) - end.().output("OffGrid.LBE", "LBE is off-grid") - -> do - PolyRes.ongrid(5.nm) - end.().output("OffGrid.PolyRes", "PolyRes is off-grid") -end - -puts("Number of DRC errors: #{$drc_error_count}") -puts("Runtime in seconds: %.1f" % [Time.now - $start_time]) - - diff --git a/flow/test/test_klayout_sh.py b/flow/test/test_klayout_sh.py index 7e31a86bfa..f91368e4c9 100644 --- a/flow/test/test_klayout_sh.py +++ b/flow/test/test_klayout_sh.py @@ -49,7 +49,7 @@ def test_ruby_deck_still_needs_klayout(self): "-rd", "report_file=6_drc.lyrdb", "-r", - "/flow/platforms/ihp-sg13g2/drc/sg13g2_maximal.lydrc", + "deck.lydrc", ) self.assertEqual(result.returncode, 1) self.assertIn("Error: KLayout not found", result.stdout) From d1148f04af6011129c0f3c0bf3289d3730f6a00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 10:03:28 +0100 Subject: [PATCH 09/11] flow: deprecate Ruby KLayout decks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every deck ORFS runs is now Python on the klayout module. A deck in KLayout's Ruby DRC language, a platform outside ORFS for one, still runs through the KLayout application, and make drc / make lvs say it is deprecated: WARNING: is a Ruby KLayout deck; Ruby decks are deprecated and need a KLayout application you install yourself, see docs/user/KLayoutPythonDecks.md There is no schedule for removing Ruby support; that is up to the ORFS maintainers. docs/user/KLayoutPythonDecks.md says how to convert a deck, with ORFS's decks as worked examples. flow/util/klayout_compare_decks.py runs a Ruby deck and its translation on one GDS file and compares the violations, count and markers per category, as the conversion of ORFS's decks was checked. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018PFLaCzFtFbnWE4nPTDwpC Signed-off-by: Øyvind Harboe --- docs/toc.yml | 2 + docs/user/KLayoutPythonDecks.md | 75 +++++++++++++++++++++ flow/Makefile | 2 + flow/util/klayout_compare_decks.py | 105 +++++++++++++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 docs/user/KLayoutPythonDecks.md create mode 100644 flow/util/klayout_compare_decks.py diff --git a/docs/toc.yml b/docs/toc.yml index 3aa3ca7279..177a8424a9 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -23,6 +23,8 @@ entries: title: Flow Variables - file: user/AutoMemories.md title: Generated Memory Macros (AUTO_MEMORIES) + - file: user/KLayoutPythonDecks.md + title: KLayout DRC and LVS Decks in Python - file: user/LargeDesigns.md title: Tips on building large design - file: contrib/Metrics.md diff --git a/docs/user/KLayoutPythonDecks.md b/docs/user/KLayoutPythonDecks.md new file mode 100644 index 0000000000..d5f1b5efd7 --- /dev/null +++ b/docs/user/KLayoutPythonDecks.md @@ -0,0 +1,75 @@ +# KLayout DRC and LVS decks in Python + +`make drc` and `make lvs` run a platform's KLayout deck. A deck written in +Python (`KLAYOUT_DRC_FILE` or `KLAYOUT_LVS_FILE` ending in `.py`) runs with +the `klayout` Python module from pip, which the ORFS Python requirements +install. A deck written in KLayout's Ruby DRC language (`.lydrc`, `.lylvs`) +needs the KLayout application. + +The decks ORFS runs are Python: asap7, nangate45, sky130hd and +ihp-sg13g2 (`sg13g2_minimal`) DRC, and sky130hd and ihp-sg13g2 LVS. + +## Ruby decks are deprecated + +A Ruby deck still runs, through the KLayout application, and `make drc` and +`make lvs` print a warning when they run one. The ORFS installer installs +only the `klayout` Python module, so whoever uses a Ruby deck installs the +KLayout application themselves ([KLayout](https://www.klayout.de/build.html)). +To work with the ORFS installer, convert the deck to Python. There is no +schedule for removing Ruby support; that is up to the ORFS maintainers. + +The Python module does not include Ruby, and KLayout will not add it +([KLayout/klayout#1772](https://github.com/KLayout/klayout/issues/1772)). +The DRC language is a thin layer over `klayout.db`'s `Region`, `Edges` and +`EdgePairs`, so a deck translates to Python line by line +([KLayout/klayout#1980](https://github.com/KLayout/klayout/issues/1980)). + +## Converting a deck + +`flow/util/klayout_drc.py` is that layer for the operations ORFS's decks +use, with the DRC language's defaults: `input` and `polygons`, the checks +(`width`, `space`, `separation`, `enclosing`, `overlap`, `notch`, +`isolated`), the selections (`with_area`, `with_angle`, `with_density` +over tiles, `interacting`, `inside`, ...), boolean operations, `sized`, +`connect` with `antenna_check`, and `output`. `flow/util/klayout_lvs.py` +adds `connect_global`, `extract_devices`, `same_circuits` and `compare` +for LVS. The decks in +`flow/platforms/*/drc/*_drc.py` and `flow/platforms/*/lvs/` are +worked examples next to the Ruby they replaced (in git history). +`flow/platforms/ihp-sg13g2/lvs/sg13g2_lvs.py` shows what a deck's own Ruby +classes become: `RBA::GenericDeviceExtractor`, `RBA::GenericDeviceCombiner`, +`RBA::NetlistSpiceReaderDelegate` and device class subclasses translate to +the `klayout.db` classes of the same names, with the same callbacks +(`setup`, `get_connectivity`, `extract_devices`, `combine_devices`, +`parse_element`, `element`); `super()` calls the base implementation, as +Ruby's `super` does. + +1. Translate the deck statement by statement into a `*_drc.py` that uses + `klayout_drc`; keep the rule names, so the report categories match. +2. Check it against the Ruby deck with `flow/util/klayout_compare_decks.py`, + on the platform's layout and on a copy with shapes added that break the + rules (width, space, enclosure, density, antenna): + + ```shell + python3 flow/util/klayout_compare_decks.py \ + --ruby-deck=.lydrc --python-deck=_drc.py \ + --in-gds=results///base/6_final.gds --out-dir= + ``` + + It runs the Ruby deck in the KLayout application, the Python deck with + the module, and compares the violations per category, count and markers. +3. Point `KLAYOUT_DRC_FILE` at the Python deck. + +The conversion of ORFS's decks found a bug kept as is: in the sky130hd deck, +`if backend_flow = CU` assigns rather than compares, so its copper-only +rules never ran; the Python deck keeps `CU = False`. + +ihp-sg13g2's Ruby LVS deck never ran in ORFS. Its `config.mk` line was +commented out and named a file that did not exist; the deck reads its +inputs from `-rd input=`, `schematic=`, `topcell=` and `report=`, which +`make lvs` does not pass; and the CDL that `make` wrote left out the decap +cells that are in the layout. The Python deck gets its inputs from `klayout_lvs.py`, and +`flow/scripts/cdl.tcl` writes the CDL with `-include_fillers`. + +What only the KLayout application has is dropped: `RBA::CellView::active` +(a deck run from the GUI on the open layout) and file dialogs. diff --git a/flow/Makefile b/flow/Makefile index 566f38c881..9d3b725445 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -807,6 +807,7 @@ drc: $(REPORTS_DIR)/6_drc.lyrdb $(REPORTS_DIR)/6_drc.lyrdb: $(GDS_FINAL_FILE) $(KLAYOUT_DRC_FILE) $(if $(filter %.py,$(KLAYOUT_DRC_FILE)),$(UTILS_DIR)/klayout_drc.py,| check-klayout) ifneq ($(KLAYOUT_DRC_FILE),) + $(if $(filter %.py,$(KLAYOUT_DRC_FILE)),,@echo "WARNING: $(KLAYOUT_DRC_FILE) is a Ruby KLayout deck; Ruby decks are deprecated and need a KLayout application you install yourself, see docs/user/KLayoutPythonDecks.md") $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_drc.log) --tee -- \ $(if $(filter %.py,$(KLAYOUT_DRC_FILE)), \ $(PYTHON_EXE) $(UTILS_DIR)/klayout_drc.py \ @@ -833,6 +834,7 @@ lvs: $(RESULTS_DIR)/6_lvs.lvsdb $(RESULTS_DIR)/6_lvs.lvsdb: $(GDS_FINAL_FILE) $(KLAYOUT_LVS_FILE) $(OBJECTS_DIR)/6_final_concat.cdl $(if $(filter %.py,$(KLAYOUT_LVS_FILE)),$(UTILS_DIR)/klayout_lvs.py $(UTILS_DIR)/klayout_drc.py,| check-klayout) ifneq ($(KLAYOUT_LVS_FILE),) + $(if $(filter %.py,$(KLAYOUT_LVS_FILE)),,@echo "WARNING: $(KLAYOUT_LVS_FILE) is a Ruby KLayout deck; Ruby decks are deprecated and need a KLayout application you install yourself, see docs/user/KLayoutPythonDecks.md") $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_lvs.log) --tee -- \ $(if $(filter %.py,$(KLAYOUT_LVS_FILE)), \ $(PYTHON_EXE) $(UTILS_DIR)/klayout_lvs.py \ diff --git a/flow/util/klayout_compare_decks.py b/flow/util/klayout_compare_decks.py new file mode 100644 index 0000000000..b8079b67b3 --- /dev/null +++ b/flow/util/klayout_compare_decks.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Compare a Ruby KLayout DRC deck with its Python translation. + +Runs the Ruby deck in the KLayout application and the Python deck with the +klayout Python module on the same GDS file, then compares the violations +per category: the count and the marker geometry. Exits 0 when they agree, +1 when they differ. + +For converting a deck to Python before the Ruby decks stop working; see +docs/user/KLayoutPythonDecks.md. A clean layout proves little: also run it +on a copy of the layout with shapes added that break the rules. +""" + +import argparse +import collections +import os +import shutil +import subprocess +import sys + +import klayout.rdb as rdb + +UTIL_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def violations(path): + report = rdb.ReportDatabase() + report.load(path) + names = {} + + def walk(categories, prefix): + for category in categories: + name = prefix + category.name() + names[category.rdb_id()] = name + walk(category.each_sub_category(), name + ".") + + walk(report.each_category(), "") + markers = collections.defaultdict(collections.Counter) + for item in report.each_item(): + name = names[item.category_id()] + for value in item.each_value(): + markers[name][value.to_s()] += 1 + return markers + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("--ruby-deck", required=True) + parser.add_argument("--python-deck", required=True) + parser.add_argument("--in-gds", required=True) + parser.add_argument("--out-dir", required=True) + parser.add_argument( + "--klayout", + default=os.environ.get("KLAYOUT_CMD") or shutil.which("klayout"), + help="the KLayout application, default KLAYOUT_CMD or klayout on PATH", + ) + args = parser.parse_args() + if not args.klayout: + sys.exit("ERROR: the KLayout application is needed to run the Ruby deck") + + os.makedirs(args.out_dir, exist_ok=True) + ruby_report = os.path.join(args.out_dir, "ruby.lyrdb") + python_report = os.path.join(args.out_dir, "python.lyrdb") + subprocess.run( + [ + args.klayout, + "-b", + "-rd", + "in_gds=" + args.in_gds, + "-rd", + "report_file=" + ruby_report, + "-r", + args.ruby_deck, + ], + check=True, + ) + subprocess.run( + [ + sys.executable, + os.path.join(UTIL_DIR, "klayout_drc.py"), + "--deck=" + args.python_deck, + "--in-gds=" + args.in_gds, + "--report-file=" + python_report, + ], + check=True, + ) + + ruby = violations(ruby_report) + python = violations(python_report) + same = True + for name in sorted(set(ruby) | set(python)): + ruby_count = sum(ruby[name].values()) + python_count = sum(python[name].values()) + differs = ruby[name] != python[name] + same = same and not differs + print( + "%-40s ruby %6d python %6d%s" + % (name, ruby_count, python_count, " <-- differs" if differs else "") + ) + print("same" if same else "DIFFERENT") + sys.exit(0 if same else 1) + + +if __name__ == "__main__": + main() From c27524a0bbfa74fbd7aed2b97d589397a5d5dde7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Fri, 6 Nov 2026 10:04:21 +0100 Subject: [PATCH 10/11] installer: no KLayout application MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flow writes its GDS file and runs its DRC and LVS decks with KLayout's Python module, from the installer's Python packages, so the installer no longer installs the KLayout application. Deleted: - etc/DependencyInstaller.sh: klayoutVersion, and the KLayout install for each OS: the CentOS 7, CentOS 8 and Rocky Linux 9 RPMs with their version check and reinstall, the Ubuntu .deb with its md5 table per release and the "Unsupported Ubuntu version" exit, the build from source on Ubuntu aarch64 with its Qt, Ruby and libgit2 build dependencies, `klayout` from apt on rodete and the Homebrew cask on macOS. - env.sh: the KLayout.app PATH entry on macOS. - flow/scripts/variables.mk: the lookup of a KLayout built into tools/install/klayout, which nothing installs. KLAYOUT_CMD is the klayout on PATH unless set, and is exported bare. - docs/user/BuildWithPrebuilt.md: the KLayout version note and the LD_LIBRARY_PATH line for a KLayout built from source. A deprecated Ruby DRC/LVS deck, the gallery and the viewers still need the KLayout application, installed by those who use them. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018PFLaCzFtFbnWE4nPTDwpC Signed-off-by: Øyvind Harboe --- docs/user/BuildWithPrebuilt.md | 14 ++--- env.sh | 1 - etc/DependencyInstaller.sh | 109 ++------------------------------- flow/scripts/variables.mk | 15 +---- 4 files changed, 14 insertions(+), 125 deletions(-) diff --git a/docs/user/BuildWithPrebuilt.md b/docs/user/BuildWithPrebuilt.md index 254ee927ce..c21c0c0999 100644 --- a/docs/user/BuildWithPrebuilt.md +++ b/docs/user/BuildWithPrebuilt.md @@ -1,10 +1,13 @@ # Using Pre-built Binaries -## Install Klayout and Yosys -Please ensure the Klayout version (denoted with `klayoutVersion` variable) is consistent with the one used in [DependencyInstaller script](https://github.com/The-OpenROAD-Project/OpenROAD-flow-scripts/blob/master/etc/DependencyInstaller.sh). +## Install KLayout and Yosys +The GDS file, DRC and LVS use KLayout's Python module, installed with the +other Python packages from `etc/requirements-common_lock.txt`, or alone +with `pip install klayout`. The KLayout application is needed only for a +deprecated Ruby DRC/LVS deck (see [KLayoutPythonDecks.md](KLayoutPythonDecks.md)), +the gallery and the viewers: +[KLayout](https://www.klayout.de/build.html). -Instructions for installing: -- [Klayout>=0.28.8](https://www.klayout.de/build.html) - [Yosys>=0.58](https://github.com/YosysHQ/oss-cad-suite-build/blob/master/README.md#installation) ## Install OpenROAD @@ -44,9 +47,6 @@ Export path variables accordingly. export OPENROAD_EXE=$(command -v openroad) export YOSYS_EXE=$(command -v yosys) -# only if KLayout is built from source -export LD_LIBRARY_PATH="/bin:$PATH" - yosys -help yosys -m slang -p "slang_version" openroad -help diff --git a/env.sh b/env.sh index e4194c306f..a1a99fdd34 100755 --- a/env.sh +++ b/env.sh @@ -16,7 +16,6 @@ function __setpaths() { export PATH=${DIR}/tools/install/kepler-formal/bin:$PATH if [[ "$OSTYPE" == "darwin"* ]]; then - export PATH="/Applications/KLayout/klayout.app/Contents/MacOS:$PATH" export PATH="$(brew --prefix bison)/bin:$(brew --prefix flex)/bin:$(brew --prefix tcl-tk@8)/bin:$PATH" export QT_QPA_PLATFORM=cocoa fi diff --git a/etc/DependencyInstaller.sh b/etc/DependencyInstaller.sh index d98925a0a3..553d555698 100755 --- a/etc/DependencyInstaller.sh +++ b/etc/DependencyInstaller.sh @@ -12,8 +12,6 @@ fi # Make sure we are on the correct folder before beginning cd "${_script_dir}/../" -# package versions -klayoutVersion=0.30.12 if [[ "$OSTYPE" == "darwin"* ]]; then numThreads=$(sysctl -n hw.logicalcpu) else @@ -203,19 +201,6 @@ _install_EL7_Packages() { ruby-devel \ tcl-tclreadline \ tcl-tclreadline-devel - - if ! [ -x "$(command -v klayout)" ]; then - yum -y install https://www.klayout.org/downloads/CentOS_7/klayout-${klayoutVersion}-0.x86_64.rpm - else - currentVersion=$(klayout -v | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') - if _versionCompare $currentVersion -ge $klayoutVersion; then - echo "KLayout version greater than or equal to ${klayoutVersion}" - else - echo "KLayout version less than ${klayoutVersion}" - sudo yum remove -y klayout - yum -y install https://www.klayout.org/downloads/CentOS_7/klayout-${klayoutVersion}-0.x86_64.rpm - fi - fi } @@ -226,7 +211,7 @@ _install_EL8_EL9_CleanUp() { # Enterprise Linux 8/9 package installation (EL8/EL9 = RHEL, Rocky Linux, AlmaLinux, or CentOS 8 as no CentOS 9 exists) _install_EL8_EL9_Packages() { - # Re-detect EL version for appropriate KLayout package + # Re-detect EL version if [[ -f /etc/os-release ]]; then elVersion=$(awk -F= '/^VERSION_ID/{print $2}' /etc/os-release | sed 's/"//g' | cut -d. -f1) else @@ -286,42 +271,6 @@ _install_EL8_EL9_Packages() { dnf -y install \ https://mirror.stream.centos.org/9-stream/AppStream/x86_64/os/Packages/readline-devel-8.1-4.el9.x86_64.rpm fi - - # Install KLayout based on EL version, note the different URLs - case "${elVersion}" in - "8") - if ! [ -x "$(command -v klayout)" ]; then - dnf -y install https://www.klayout.org/downloads/CentOS_8/klayout-${klayoutVersion}-0.x86_64.rpm - else - currentVersion=$(klayout -v | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') - if _versionCompare "$currentVersion" -ge $klayoutVersion; then - echo "KLayout version greater than or equal to ${klayoutVersion}" - else - echo "KLayout version less than ${klayoutVersion}" - sudo dnf remove -y klayout - dnf -y install https://www.klayout.org/downloads/CentOS_8/klayout-${klayoutVersion}-0.x86_64.rpm - fi - fi - ;; - "9") - if ! [ -x "$(command -v klayout)" ]; then - dnf -y install https://www.klayout.org/downloads/RockyLinux_9/klayout-${klayoutVersion}-0.x86_64.rpm - else - currentVersion=$(klayout -v | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') - if _versionCompare "$currentVersion" -ge $klayoutVersion; then - echo "KLayout version greater than or equal to ${klayoutVersion}" - else - echo "KLayout version less than ${klayoutVersion}" - sudo dnf remove -y klayout - dnf -y install https://www.klayout.org/downloads/RockyLinux_9/klayout-${klayoutVersion}-0.x86_64.rpm - fi - fi - ;; - *) - echo "ERROR: Unsupported Enterprise Linux version: ${elVersion}" - exit 1 - ;; - esac } _installUbuntuCleanUp() { @@ -329,19 +278,6 @@ _installUbuntuCleanUp() { apt-get autoremove -y } -_installKlayoutDependenciesUbuntuAarch64() { - echo "Installing Klayout dependencies" - export DEBIAN_FRONTEND=noninteractive - apt-get -y update - apt-get -y install build-essential \ - qtbase5-dev qttools5-dev libqt5xmlpatterns5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5svg5-dev \ - ruby ruby-dev \ - python3 python3-dev \ - libz-dev\ - libgit2-dev - echo "All dependencies installed successfully" -} - _installUbuntuPackages() { export DEBIAN_FRONTEND="noninteractive" apt-get -y update @@ -399,44 +335,8 @@ _installUbuntuPackages() { fi apt-get install -y --no-install-recommends ${packages[@]} - # install KLayout - if [[ $1 == "rodete" ]]; then - apt-get -y install --no-install-recommends klayout python3-pandas - else - arch=$(uname -m) - lastDir="$(pwd)" - # temp dir to download and compile - baseDir=/tmp/installers - klayoutPrefix=${PREFIX:-"/usr/local"} - mkdir -p "${baseDir}" - cd "${baseDir}" - if [[ $arch == "aarch64" ]]; then - if [ ! -f ${klayoutPrefix}/klayout ]; then - _installKlayoutDependenciesUbuntuAarch64 - echo "Installing KLayout for aarch64 architecture" - git clone --depth=1 -b "v${klayoutVersion}" https://github.com/KLayout/klayout.git - cd klayout - ./build.sh -bin "${klayoutPrefix}" -option -j "${numThreads}" - else - echo "Klayout is already installed" - fi - else - if [[ $1 == 22.04 ]]; then - klayoutChecksum=6dfffa50f385881768dee30bdc759608 - elif [[ $1 == 24.04 ]]; then - klayoutChecksum=0181713e60891e461d6d1664c1b8e213 - elif [[ $1 == 26.04 ]]; then - klayoutChecksum=904f60fd05c3a5029cab8a2840029f49 - else - echo "Unsupported Ubuntu version $1. KLayout ${klayoutVersion} packages are pinned for 22.04, 24.04, and 26.04. Please upgrade to a supported LTS release or install KLayout ${klayoutVersion} manually from https://www.klayout.org/build.html" - exit 1 - fi - wget https://www.klayout.org/downloads/Ubuntu-${1%.*}/klayout_${klayoutVersion}-1_amd64.deb - md5sum -c <(echo "${klayoutChecksum} klayout_${klayoutVersion}-1_amd64.deb") || exit 1 - dpkg -i klayout_${klayoutVersion}-1_amd64.deb - fi - cd "${lastDir}" - rm -rf "${baseDir}" + if [[ $1 == "rodete" ]]; then + apt-get -y install --no-install-recommends python3-pandas fi if command -v docker &> /dev/null; then @@ -473,7 +373,6 @@ _installDarwinPackages() { brew install libffi tcl-tk ruby brew install python libomp doxygen capnp tbb bison flex boost spdlog zlib cmake brew link --force libomp - brew install --cask klayout brew install docker docker-buildx } @@ -676,7 +575,7 @@ case "${os}" in # First install OpenROAD base _installORDependencies - # Determine between EL7 vs EL8/9, since yum vs dnf should be used, and different Klayout builds exist + # Determine between EL7 vs EL8/9, since yum vs dnf should be used case "${elVersion}" in "7") # EL7 = RHEL 7 or CentOS 7 diff --git a/flow/scripts/variables.mk b/flow/scripts/variables.mk index fd48cba625..f4522665db 100644 --- a/flow/scripts/variables.mk +++ b/flow/scripts/variables.mk @@ -132,22 +132,13 @@ export YOSYS_EXE ?= $(abspath $(FLOW_HOME)/../tools/install/yosys/bin/yosys) YOSYS_IS_VALID := $(if $(YOSYS_EXE),$(shell test -x $(YOSYS_EXE) && echo "true"),) -# Use locally installed and built klayout if it exists, otherwise use klayout in path -KLAYOUT_DIR = $(abspath $(FLOW_HOME)/../tools/install/klayout/) -KLAYOUT_BIN_FROM_DIR = $(KLAYOUT_DIR)/klayout - KEPLER_FORMAL_EXE ?= $(abspath $(FLOW_HOME)/../tools/install/kepler-formal/bin/kepler-formal) export KEPLER_FORMAL_EXE -ifeq ($(wildcard $(KLAYOUT_BIN_FROM_DIR)), $(KLAYOUT_BIN_FROM_DIR)) -KLAYOUT_CMD ?= sh -c 'LD_LIBRARY_PATH=$(dir $(KLAYOUT_BIN_FROM_DIR)) $$0 "$$@"' $(KLAYOUT_BIN_FROM_DIR) -else -ifeq ($(KLAYOUT_CMD),) -KLAYOUT_CMD ?= $(shell command -v klayout) +ifeq ($(origin KLAYOUT_CMD), undefined) +KLAYOUT_CMD := $(shell command -v klayout) endif -endif - -export KLAYOUT_CMD := $(KLAYOUT_CMD) +export KLAYOUT_CMD #------------------------------------------------------------------------------- WRAPPED_LEFS = $(foreach lef,$(notdir $(WRAP_LEFS)),$(OBJECTS_DIR)/lef/$(lef:.lef=_mod.lef)) From 4ee16be1addeddba4a661e0fea11c0c9cbbde7a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 9 Nov 2026 20:05:38 +0100 Subject: [PATCH 11/11] klayout: module on the default Linux path, decks tested in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setup.sh on Linux now installs etc/requirements-common_lock.txt, the klayout module among them, into the checkout's .venv, and env.sh puts .venv/bin first on PATH, so the flow's PYTHON_EXE (python3 on PATH) has the module. On Ubuntu 24.04/26.04 and rodete the installer skipped the lock unless VIRTUAL_ENV was set, so after `sudo ./setup.sh` the GDS merge, make drc and make lvs all failed for want of the module. DependencyInstaller.sh -common on an externally managed Python (Ubuntu 23.04 and later, rodete) without VIRTUAL_ENV now stops with an error instead of a note. A new installer CI job runs setup.sh as a normal user in ubuntu:24.04 and ubuntu:26.04, without VIRTUAL_ENV, and checks that PYTHON_EXE is .venv/bin/python3 and imports klayout.db. make drc and make lvs add the check-klayout guard only for a non-empty deck that is not Python; an empty deck again writes "DRC/LVS not supported" without a KLayout application (nangate45 LVS, gf180 DRC, ...). The util-test workflow installs the Python lock into a venv, so the klayout tests run instead of being skipped, and runs on changes to the platform decks and the lock. test_klayout_decks.py runs each platform deck through klayout_drc.py and klayout_lvs.py: DRC on three standard cells and a metal1 sliver, against the stored rule count and violation count per category; LVS on an inverter, which must match its CDL. Also: - klayout_drc.py and klayout_lvs.py check for the module before they load the deck, so a missing module prints the pip install hint instead of a traceback from the deck's import. - Layer.corners() with no argument selects all corners, -180..180, as the DRC language does; it selected -180 only. - KLAYOUT_DRC_FILE and KLAYOUT_LVS_FILE are in variables.yaml. - docker_shell passes KLAYOUT_CMD only when it is set, instead of defaulting it to /usr/bin/klayout, which the images no longer have. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Øyvind Harboe --- .../github-actions-cron-test-installer.yml | 40 +++ .../github-actions-cron-util-test.yml | 14 + .gitignore | 1 + docs/user/BuildLocally.md | 4 + docs/user/FlowVariables.md | 4 + env.sh | 2 + etc/DependencyInstaller.sh | 45 ++-- flow/Makefile | 4 +- flow/scripts/variables.yaml | 14 + flow/test/test_klayout_decks.py | 240 ++++++++++++++++++ flow/test/test_klayout_drc.py | 10 + flow/util/docker_shell | 9 +- flow/util/klayout_drc.py | 25 +- flow/util/klayout_lvs.py | 1 + setup.sh | 5 +- 15 files changed, 383 insertions(+), 35 deletions(-) create mode 100644 flow/test/test_klayout_decks.py diff --git a/.github/workflows/github-actions-cron-test-installer.yml b/.github/workflows/github-actions-cron-test-installer.yml index 604824eab3..32aa089d9d 100644 --- a/.github/workflows/github-actions-cron-test-installer.yml +++ b/.github/workflows/github-actions-cron-test-installer.yml @@ -9,6 +9,7 @@ on: - '.github/workflows/github-actions-cron-test-installer.yml' - 'build_openroad.sh' - 'env.sh' + - 'setup.sh' - 'flow/Makefile' pull_request: paths: @@ -17,6 +18,7 @@ on: - '.github/workflows/github-actions-cron-test-installer.yml' - 'build_openroad.sh' - 'env.sh' + - 'setup.sh' - 'flow/Makefile' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -55,3 +57,41 @@ jobs: run: | docker rm -f "$INSTALLER_CONTAINER" || true docker image rm "openroad/flow-${{ matrix.os }}-builder:$INSTALLER_TAG" "openroad/flow-${{ matrix.os }}-dev:$INSTALLER_TAG" || true + + testSetup: + # The documented path: sudo ./setup.sh by a normal user, no VIRTUAL_ENV + # set, then env.sh; the flow's PYTHON_EXE must have the klayout module. + strategy: + fail-fast: false + matrix: + os: ["ubuntu:26.04", "ubuntu:24.04"] + runs-on: ${{ vars.USE_SELF_HOSTED == 'true' && 'self-hosted-ubuntu-26.04' || 'ubuntu-26.04' }} + steps: + - name: Check out repository code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 1 + submodules: recursive + - name: Run setup.sh as a user and check PYTHON_EXE + run: | + script=$(cat <<'EOF' + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y --no-install-recommends sudo git ca-certificates + useradd -m orfs + cp -a /src /home/orfs/orfs + chown -R orfs: /home/orfs/orfs + git config --system --add safe.directory '*' + cd /home/orfs/orfs + unset VIRTUAL_ENV + SUDO_USER=orfs ./setup.sh + sudo -u orfs bash -ex <<'USER' + test -z "${VIRTUAL_ENV:-}" + source ./env.sh + python_exe=$(make -s -C flow print-PYTHON_EXE | sed -n 's/^PYTHON_EXE: //p') + test "$python_exe" = /home/orfs/orfs/.venv/bin/python3 + "$python_exe" -c "import klayout.db" + USER + EOF + ) + docker run --rm -v "$PWD:/src:ro" "${{ matrix.os }}" bash -exc "$script" diff --git a/.github/workflows/github-actions-cron-util-test.yml b/.github/workflows/github-actions-cron-util-test.yml index 9c287dad9b..5b9aa02b6f 100644 --- a/.github/workflows/github-actions-cron-util-test.yml +++ b/.github/workflows/github-actions-cron-util-test.yml @@ -6,10 +6,18 @@ on: paths: - 'flow/util/*.py' - 'flow/test/test_*.py' + - 'flow/platforms/*/drc/*.py' + - 'flow/platforms/*/lvs/*.py' + - 'etc/requirements-common_lock.txt' + - '.github/workflows/github-actions-cron-util-test.yml' pull_request: paths: - 'flow/util/*.py' - 'flow/test/test_*.py' + - 'flow/platforms/*/drc/*.py' + - 'flow/platforms/*/lvs/*.py' + - 'etc/requirements-common_lock.txt' + - '.github/workflows/github-actions-cron-util-test.yml' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -27,8 +35,14 @@ jobs: with: fetch-depth: 1 submodules: recursive + - name: Install Python packages + run: | + python3 -m venv venv + venv/bin/python3 -m pip install --no-cache-dir --upgrade --require-hashes -r etc/requirements-pip_lock.txt + venv/bin/python3 -m pip install --no-cache-dir -r etc/requirements-common_lock.txt - name: Run Tests run: | + source venv/bin/activate cd flow/test for file in *.py; do python3 "$file" diff --git a/.gitignore b/.gitignore index 84d1e39569..989fed68d6 100644 --- a/.gitignore +++ b/.gitignore @@ -107,4 +107,5 @@ MODULE.bazel.lock # python venv venv/ +.venv/ tmp/ diff --git a/docs/user/BuildLocally.md b/docs/user/BuildLocally.md index 15ccf7ff9c..3385b8a44d 100644 --- a/docs/user/BuildLocally.md +++ b/docs/user/BuildLocally.md @@ -12,6 +12,10 @@ cd OpenROAD-flow-scripts sudo ./setup.sh ``` +`setup.sh` installs the Python packages the flow uses, the `klayout` +module that writes GDS among them, into a virtual environment in `.venv`, +which `env.sh` puts first on `PATH`. + ## Using Bazel to build OpenROAD and run the ORFS flow (unsupported) For ORFS/OpenROAD developers. Most of `./setup.sh` isn't needed when diff --git a/docs/user/FlowVariables.md b/docs/user/FlowVariables.md index 0df148d71d..b74340a9bc 100644 --- a/docs/user/FlowVariables.md +++ b/docs/user/FlowVariables.md @@ -176,6 +176,8 @@ configuration file. | IO_PLACER_V| A list of metal layers on which the I/O pins are placed vertically (sides of the die).| | | IR_DROP_LAYER| Default metal layer to report IR drop.| | | KEEP_VARS| Feature toggle to keep intermediate variables during the flow. This is useful for the single-run flow, where all stages of the flow are run in a single OpenROAD instance.| 0| +| KLAYOUT_DRC_FILE| The KLayout DRC deck `make drc` runs. A `.py` deck runs with the klayout Python module; a Ruby deck (`.lydrc`) is deprecated and needs the KLayout application. Empty when the platform has no deck.| | +| KLAYOUT_LVS_FILE| The KLayout LVS deck `make lvs` runs. A `.py` deck runs with the klayout Python module; a Ruby deck (`.lylvs`) is deprecated and needs the KLayout application. Empty when the platform has no deck.| | | KLAYOUT_TECH_FILE| A mapping from LEF/DEF to GDS using the KLayout tool.| | | LATCH_MAP_FILE| Optional mapping file supplied to Yosys to map latches| | | LAYER_PARASITICS_FILE| Path to per layer parasitics file. Defaults to $(PLATFORM_DIR)/setRC.tcl.| | @@ -632,6 +634,8 @@ configuration file. - [CDL_FILE](#CDL_FILE) - [GDS_ALLOW_EMPTY](#GDS_ALLOW_EMPTY) - [GND_NETS_VOLTAGES](#GND_NETS_VOLTAGES) +- [KLAYOUT_DRC_FILE](#KLAYOUT_DRC_FILE) +- [KLAYOUT_LVS_FILE](#KLAYOUT_LVS_FILE) - [LEC_AUX_VERILOG_FILES](#LEC_AUX_VERILOG_FILES) - [LEC_CHECK](#LEC_CHECK) - [MAX_ROUTING_LAYER](#MAX_ROUTING_LAYER) diff --git a/env.sh b/env.sh index a1a99fdd34..9916f33836 100755 --- a/env.sh +++ b/env.sh @@ -14,6 +14,8 @@ function __setpaths() { export PATH=${DIR}/tools/install/OpenROAD/bin:$PATH export PATH=${DIR}/tools/install/yosys/bin:$PATH export PATH=${DIR}/tools/install/kepler-formal/bin:$PATH + # The Python packages setup.sh installs, the klayout module among them + export PATH=${DIR}/.venv/bin:$PATH if [[ "$OSTYPE" == "darwin"* ]]; then export PATH="$(brew --prefix bison)/bin:$(brew --prefix flex)/bin:$(brew --prefix tcl-tk@8)/bin:$PATH" diff --git a/etc/DependencyInstaller.sh b/etc/DependencyInstaller.sh index 553d555698..48a7fff4ce 100755 --- a/etc/DependencyInstaller.sh +++ b/etc/DependencyInstaller.sh @@ -623,31 +623,30 @@ case "${os}" in if [[ "${option}" == "common" || "${option}" == "all" ]]; then _installCmake fi - if [[ $version != "rodete" ]]; then - if _versionCompare ${version} -lt 23.04 ; then - if [[ "${option}" == "common" || "${option}" == "all" ]]; then - _installPipCommon - fi - else - # The system Python is externally managed (PEP 668): its - # packages come from apt, in -base with the other system - # packages. The lock, which also has packages apt does not - # carry, goes into a virtual environment when one is named. - if [[ "${option}" == "base" || "${option}" == "all" ]]; then - _installPipSystem - fi - if [[ "${option}" == "common" || "${option}" == "all" ]]; then - if [[ -n "${VIRTUAL_ENV:-}" ]]; then - _installPipCommon - else - echo "Python packages from apt only. For those in" \ - "etc/requirements-common_lock.txt that apt does" \ - "not carry, set VIRTUAL_ENV and run -common again." - fi - fi + if [[ $version != "rodete" ]] && _versionCompare ${version} -lt 23.04 ; then + if [[ "${option}" == "common" || "${option}" == "all" ]]; then + _installPipCommon fi else - echo "Skip pip packages for rodete" + # The system Python is externally managed (PEP 668): its + # packages come from apt, in -base with the other system + # packages. The lock, which also has packages apt does not + # carry, among them the klayout module the flow writes GDS + # with, goes into the virtual environment VIRTUAL_ENV names; + # setup.sh names the checkout's .venv. + if [[ $version != "rodete" ]] && [[ "${option}" == "base" || "${option}" == "all" ]]; then + _installPipSystem + fi + if [[ "${option}" == "common" || "${option}" == "all" ]]; then + if [[ -z "${VIRTUAL_ENV:-}" ]]; then + echo "ERROR: the system Python is externally managed;" \ + "set VIRTUAL_ENV to the virtual environment to" \ + "install etc/requirements-common_lock.txt into," \ + "or run setup.sh, which installs it into .venv." >&2 + exit 1 + fi + _installPipCommon + fi fi ;; "Darwin" ) diff --git a/flow/Makefile b/flow/Makefile index e2615576bd..331041fda7 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -802,7 +802,7 @@ $(GDS_FINAL_FILE): $(GDS_MERGED_FILE) .PHONY: drc drc: $(REPORTS_DIR)/6_drc.lyrdb -$(REPORTS_DIR)/6_drc.lyrdb: $(GDS_FINAL_FILE) $(KLAYOUT_DRC_FILE) $(if $(filter %.py,$(KLAYOUT_DRC_FILE)),$(UTILS_DIR)/klayout_drc.py,| check-klayout) +$(REPORTS_DIR)/6_drc.lyrdb: $(GDS_FINAL_FILE) $(KLAYOUT_DRC_FILE) $(if $(filter %.py,$(KLAYOUT_DRC_FILE)),$(UTILS_DIR)/klayout_drc.py) $(if $(filter-out %.py,$(KLAYOUT_DRC_FILE)),| check-klayout) ifneq ($(KLAYOUT_DRC_FILE),) $(if $(filter %.py,$(KLAYOUT_DRC_FILE)),,@echo "WARNING: $(KLAYOUT_DRC_FILE) is a Ruby KLayout deck; Ruby decks are deprecated and need a KLayout application you install yourself, see docs/user/KLayoutPythonDecks.md") $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_drc.log) --tee -- \ @@ -829,7 +829,7 @@ $(OBJECTS_DIR)/6_final_concat.cdl: $(RESULTS_DIR)/6_final.cdl $(CDL_FILE) .PHONY: lvs lvs: $(RESULTS_DIR)/6_lvs.lvsdb -$(RESULTS_DIR)/6_lvs.lvsdb: $(GDS_FINAL_FILE) $(KLAYOUT_LVS_FILE) $(OBJECTS_DIR)/6_final_concat.cdl $(if $(filter %.py,$(KLAYOUT_LVS_FILE)),$(UTILS_DIR)/klayout_lvs.py $(UTILS_DIR)/klayout_drc.py,| check-klayout) +$(RESULTS_DIR)/6_lvs.lvsdb: $(GDS_FINAL_FILE) $(KLAYOUT_LVS_FILE) $(OBJECTS_DIR)/6_final_concat.cdl $(if $(filter %.py,$(KLAYOUT_LVS_FILE)),$(UTILS_DIR)/klayout_lvs.py $(UTILS_DIR)/klayout_drc.py) $(if $(filter-out %.py,$(KLAYOUT_LVS_FILE)),| check-klayout) ifneq ($(KLAYOUT_LVS_FILE),) $(if $(filter %.py,$(KLAYOUT_LVS_FILE)),,@echo "WARNING: $(KLAYOUT_LVS_FILE) is a Ruby KLayout deck; Ruby decks are deprecated and need a KLayout application you install yourself, see docs/user/KLayoutPythonDecks.md") $(RUN_CMD) --log $(abspath $(LOG_DIR)/6_lvs.log) --tee -- \ diff --git a/flow/scripts/variables.yaml b/flow/scripts/variables.yaml index ac9d582494..14462d439e 100644 --- a/flow/scripts/variables.yaml +++ b/flow/scripts/variables.yaml @@ -1051,6 +1051,20 @@ FILL_CONFIG: KLAYOUT_TECH_FILE: description: | A mapping from LEF/DEF to GDS using the KLayout tool. +KLAYOUT_DRC_FILE: + description: > + The KLayout DRC deck `make drc` runs. A `.py` deck runs with the klayout + Python module; a Ruby deck (`.lydrc`) is deprecated and needs the KLayout + application. Empty when the platform has no deck. + stages: + - final +KLAYOUT_LVS_FILE: + description: > + The KLayout LVS deck `make lvs` runs. A `.py` deck runs with the klayout + Python module; a Ruby deck (`.lylvs`) is deprecated and needs the KLayout + application. Empty when the platform has no deck. + stages: + - final IR_DROP_LAYER: description: | Default metal layer to report IR drop. diff --git a/flow/test/test_klayout_decks.py b/flow/test/test_klayout_decks.py new file mode 100644 index 0000000000..f61d3737d4 --- /dev/null +++ b/flow/test/test_klayout_decks.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +"""The platform DRC and LVS decks run through klayout_drc.py and +klayout_lvs.py, as `make drc` and `make lvs` run them, on a few standard +cells from the platform's own library. + +DRC: the cells abutted in a row, and a metal1 sliver that breaks the +width rule, so a deck that checks nothing does not pass. The rule count +and the violation count per category are the stored ones. A change to a +deck, to klayout_drc.py or to the klayout module that changes a count +shows here; update the count when the change is intended. + +LVS: a standard cell's layout matches its CDL subcircuit. + +Runs on the klayout Python module, and is skipped without it. +""" + +import os +import subprocess +import sys +import tempfile +import unittest + +try: + import klayout.db as db + import klayout.rdb as rdb +except ImportError: + db = None + +FLOW = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +PLATFORMS = os.path.join(FLOW, "platforms") +UTIL = os.path.join(FLOW, "util") + + +def platform(*path): + return os.path.join(PLATFORMS, *path) + + +def write_row(lib_gds, cells, sliver, out_gds): + """The cells, abutted in a row in a new top cell, and below them a + sliver, a 10 nm wide box that breaks the layer's width rule; the + library's other cells are dropped.""" + layout = db.Layout() + layout.read(lib_gds) + top = layout.create_cell("SMOKE") + layer, datatype = sliver + top.shapes(layout.layer(layer, datatype)).insert( + db.DBox(0, -5, 0.01, -4).to_itype(layout.dbu) + ) + x = 0 + for name in cells: + cell = layout.cell(name) + box = cell.bbox() + top.insert( + db.CellInstArray(cell.cell_index(), db.Trans(db.Vector(x - box.left, 0))) + ) + x += box.width() + for index in list(layout.each_top_cell()): + if index != top.cell_index(): + layout.prune_cell(index, -1) + layout.write(out_gds) + + +def write_cell(lib_gds, cell_name, out_gds): + """The cell alone, as the top cell.""" + layout = db.Layout() + layout.read(lib_gds) + keep = layout.cell(cell_name).cell_index() + for index in list(layout.each_top_cell()): + if index != keep: + layout.prune_cell(index, -1) + layout.write(out_gds) + + +def run(script, args): + return subprocess.run( + [sys.executable, os.path.join(UTIL, script)] + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + + +# platform: deck, library, cells, sliver layer, rule categories, violations +DRC = { + "asap7": ( + platform("asap7", "drc", "asap7_drc.py"), + platform("asap7", "gds", "asap7sc7p5t_28_R_220121a.gds"), + ["INVx1_ASAP7_75t_R", "NAND2x1p5_ASAP7_75t_R", "DFFHQNx2_ASAP7_75t_R"], + (19, 0), + 258, + { + "WELL.S.2": 4, + "GATE.S.1": 2, + "ACTIVE.LUP.1": 2, + "LIG.S.4-5": 1, + "M1.W.1": 1, + "V1.S.4": 1, + }, + ), + "nangate45": ( + platform("nangate45", "drc", "FreePDK45_drc.py"), + platform("nangate45", "gds", "NangateOpenCellLibrary.gds"), + ["INV_X1", "NAND2_X1", "DFF_X1"], + (11, 0), + 129, + { + "METAL1.1": 1, + }, + ), + "sky130hd": ( + platform("sky130hd", "drc", "sky130hd_drc.py"), + platform("sky130hd", "gds", "sky130_fd_sc_hd.gds"), + [ + "sky130_fd_sc_hd__inv_1", + "sky130_fd_sc_hd__nand2_1", + "sky130_fd_sc_hd__dfxtp_1", + ], + (68, 20), + 145, + { + "m1.1": 1, + "m1.6": 1, + }, + ), + "ihp-sg13g2": ( + platform("ihp-sg13g2", "drc", "sg13g2_minimal_drc.py"), + platform("ihp-sg13g2", "gds", "sg13g2_stdcell.gds"), + ["sg13g2_inv_1", "sg13g2_nand2_1", "sg13g2_dfrbp_1"], + (8, 0), + 77, + { + "AFil.g/g1": 1, + "AFil.g2/g3": 1, + "GFil.g": 1, + "M1.a": 1, + "M1.j/k": 1, + "M2.j/k": 1, + "M3.j/k": 1, + "M4.j/k": 1, + "M5.j/k": 1, + "M1Fil.h/k": 1, + "M2Fil.h/k": 1, + "M3Fil.h/k": 1, + "M4Fil.h/k": 1, + "M5Fil.h/k": 1, + "TM1.c/d": 1, + "TM2.c/d": 1, + }, + ), +} + +# platform: deck, library, CDL, cell +LVS = { + "sky130hd": ( + platform("sky130hd", "lvs", "sky130hd_lvs.py"), + platform("sky130hd", "gds", "sky130_fd_sc_hd.gds"), + platform("sky130hd", "cdl", "sky130hd.cdl"), + "sky130_fd_sc_hd__inv_1", + ), + "ihp-sg13g2": ( + platform("ihp-sg13g2", "lvs", "sg13g2_lvs.py"), + platform("ihp-sg13g2", "gds", "sg13g2_stdcell.gds"), + platform("ihp-sg13g2", "cdl", "sg13g2_stdcell.cdl"), + "sg13g2_inv_1", + ), +} + + +@unittest.skipIf(db is None, "the klayout Python module is not installed") +class TestKlayoutDecks(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + + def tearDown(self): + self.tmp.cleanup() + + def drc_counts(self, name): + deck, lib_gds, cells, sliver, _, _ = DRC[name] + gds = os.path.join(self.tmp.name, "in.gds") + report = os.path.join(self.tmp.name, "out.lyrdb") + write_row(lib_gds, cells, sliver, gds) + result = run( + "klayout_drc.py", + ["--deck=" + deck, "--in-gds=" + gds, "--report-file=" + report], + ) + self.assertEqual(result.returncode, 0, result.stdout) + database = rdb.ReportDatabase("") + database.load(report) + categories = 0 + counts = {} + for category in database.each_category(): + categories += 1 + if category.num_items() > 0: + counts[category.name()] = category.num_items() + return categories, counts + + def check_drc(self, name): + categories, counts = self.drc_counts(name) + self.assertEqual(categories, DRC[name][4]) + self.assertEqual(counts, DRC[name][5]) + + def test_drc_asap7(self): + self.check_drc("asap7") + + def test_drc_nangate45(self): + self.check_drc("nangate45") + + def test_drc_sky130hd(self): + self.check_drc("sky130hd") + + def test_drc_ihp_sg13g2(self): + self.check_drc("ihp-sg13g2") + + def check_lvs(self, name): + deck, lib_gds, cdl, cell = LVS[name] + gds = os.path.join(self.tmp.name, "in.gds") + report = os.path.join(self.tmp.name, "out.lvsdb") + write_cell(lib_gds, cell, gds) + result = run( + "klayout_lvs.py", + [ + "--deck=" + deck, + "--in-gds=" + gds, + "--cdl-file=" + cdl, + "--top-cell=" + cell, + "--report-file=" + report, + ], + ) + self.assertEqual(result.returncode, 0, result.stdout) + self.assertIn("Netlists match", result.stdout) + + def test_lvs_sky130hd(self): + self.check_lvs("sky130hd") + + def test_lvs_ihp_sg13g2(self): + self.check_lvs("ihp-sg13g2") + + +if __name__ == "__main__": + unittest.main() diff --git a/flow/test/test_klayout_drc.py b/flow/test/test_klayout_drc.py index f95a0109fa..9224b9e987 100644 --- a/flow/test/test_klayout_drc.py +++ b/flow/test/test_klayout_drc.py @@ -106,6 +106,16 @@ def body(deck): # 0.08, 0.0144 and 0.09 um2 in the top cell, 0.05 um2 once in the child self.assertEqual(self.run_deck(body), {"A": 4}) + def test_corners_default_all(self): + def body(deck): + via = deck.input(2, 0) + via.corners().output("ALL") + via.corners((-180, 180)).output("RANGE") + via.corners(-180).output("ONE") + + # Two vias in the top cell, four corners each; none of them -180. + self.assertEqual(self.run_deck(body), {"ALL": 8, "RANGE": 8, "ONE": 0}) + if __name__ == "__main__": unittest.main() diff --git a/flow/util/docker_shell b/flow/util/docker_shell index 6cfe7eb961..5059984eca 100755 --- a/flow/util/docker_shell +++ b/flow/util/docker_shell @@ -64,7 +64,12 @@ get_container_engine WORKSPACE=$(pwd) YOSYS_EXE=${YOSYS_EXE:-/OpenROAD-flow-scripts/tools/install/yosys/bin/yosys} OPENROAD_EXE=${OPENROAD_EXE:-/OpenROAD-flow-scripts/tools/install/OpenROAD/bin/openroad} -KLAYOUT_CMD=${KLAYOUT_CMD:-/usr/bin/klayout} +# KLAYOUT_CMD only when the caller sets it; otherwise the flow looks for +# klayout on the image's PATH. +KLAYOUT_ENV=() +if [[ -n "${KLAYOUT_CMD:-}" ]]; then + KLAYOUT_ENV=(-e "KLAYOUT_CMD=$KLAYOUT_CMD") +fi XSOCK=/tmp/.X11-unix XAUTH=/tmp/.docker.xauth @@ -116,7 +121,7 @@ DOCKER_CMD=( -e WORK_HOME=/work -e "YOSYS_EXE=$YOSYS_EXE" -e "OPENROAD_EXE=$OPENROAD_EXE" - -e "KLAYOUT_CMD=$KLAYOUT_CMD" + "${KLAYOUT_ENV[@]}" -v "$WORKSPACE:/work:Z" --network host "${PORT_ARGS[@]}" diff --git a/flow/util/klayout_drc.py b/flow/util/klayout_drc.py index 9861830505..4ca5bcfcc1 100644 --- a/flow/util/klayout_drc.py +++ b/flow/util/klayout_drc.py @@ -197,16 +197,22 @@ def space(*opts): return _Universal("space", None, opts) +def require_db(): + """Exit with an install hint when the klayout module is missing, before + a deck that imports it is loaded.""" + if db is None: + print( + "ERROR: the klayout Python module is not installed, pip install" + " klayout (etc/requirements-common_lock.txt)" + ) + sys.exit(1) + + class Deck: """The engine: the layout, the mode, and the report database.""" def __init__(self, in_gds, report_file, description, mode="deep", threads=1): - if db is None: - print( - "ERROR: the klayout Python module is not installed, pip install" - " klayout (etc/requirements-common_lock.txt)" - ) - sys.exit(1) + require_db() self.t0 = time.time() self.layout = db.Layout() print("Reading %s .." % in_gds) @@ -735,9 +741,13 @@ def extended(self, b=0, e=0, o=0, i=0, joined=False): def end_segments(self, length, fraction=0.0): return self._new(self.data.end_segments(self.deck.v(length), fraction)) - def corners(self, amin=-180.0, amax=None): + def corners(self, amin=None, amax=None): + """corners: all corners, -180..180; corners(a): those of angle a; + corners((amin, amax)) or corners(amin, amax): those in the range.""" if isinstance(amin, tuple): amin, amax = amin + if amin is None: + amin, amax = -180.0, 180.0 amax = amin if amax is None else amax return self._new( self.data.corners(float(amin), float(amax), 1, True, True, False, False) @@ -929,6 +939,7 @@ def load_deck(path): def main(argv): args = parse_args(argv) + require_db() module = load_deck(args.deck) deck = Deck( args.in_gds, diff --git a/flow/util/klayout_lvs.py b/flow/util/klayout_lvs.py index 04d7a89567..768c9a2ff0 100644 --- a/flow/util/klayout_lvs.py +++ b/flow/util/klayout_lvs.py @@ -248,6 +248,7 @@ def parse_args(argv): def main(argv): args = parse_args(argv) + klayout_drc.require_db() module = klayout_drc.load_deck(args.deck) deck = LvsDeck( args.in_gds, diff --git a/setup.sh b/setup.sh index 23bb820f04..a5339580cf 100755 --- a/setup.sh +++ b/setup.sh @@ -51,5 +51,8 @@ fi if [[ "$OSTYPE" == "darwin"* ]]; then "$DIR/etc/DependencyInstaller.sh" -common -prefix="$DIR/dependencies" else - sudo -u $SUDO_USER "$DIR/etc/DependencyInstaller.sh" -common -prefix="$DIR/dependencies" + # The Python packages, the klayout module among them, go into + # $DIR/.venv, which env.sh puts first on PATH. + sudo -u $SUDO_USER env VIRTUAL_ENV="$DIR/.venv" \ + "$DIR/etc/DependencyInstaller.sh" -common -prefix="$DIR/dependencies" fi