diff --git a/ihp-sg13g2/libs.tech/klayout/tech/drc/sg13g2_maximal.lydrc b/ihp-sg13g2/libs.tech/klayout/tech/drc/sg13g2_maximal.lydrc index 435f9147..908a2b2a 100644 --- a/ihp-sg13g2/libs.tech/klayout/tech/drc/sg13g2_maximal.lydrc +++ b/ihp-sg13g2/libs.tech/klayout/tech/drc/sg13g2_maximal.lydrc @@ -1472,6 +1472,7 @@ 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.ext_and(NAct) ContBar_NAct = ContBar.ext_and(NAct) Cont_not_outside_NAct = Cont.not_outside(NAct) nBuLayGen_nBuLay_NBL_a = nBuLayGen_nBuLay.ext_fast_width(1.0.um) @@ -1494,6 +1495,7 @@ TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp9 = TopMetal1_slit_MIM_Slt_g_TM1_sep_tmp6.du Rsil = Rsil_all_not_interact_NWell.ext_interacting(nBuLay, inverted: true) PGate = Gate.outside(NGate) PAct_NWell = PAct.ext_and(X1) +Cont_PAct = Cont.ext_and(PAct) ContBar_PAct = ContBar.ext_and(PAct) Cont_not_outside_PAct = Cont.not_outside(PAct) NActHV = NAct.ext_not(NActLV) @@ -1893,6 +1895,15 @@ end.().output("Cnt.b", "Min. Cont space = 0.18") 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 (Note 1) = 0.20") +-> do + Cont_outside_EdgeSeal.ext_fast_enclosed(Act_Not_EdgeSeal, 0.07.um, polygon_output: true) +end.().output("Cnt.c", "Min. Activ enclosure of Cont = 0.07") +-> do + pSD.ext_fast_separation(Cont_NAct, 0.09.um, max_angle: 0, include_max_angle: true, polygon_output: true) +end.().output("Cnt.g1", "Min. pSD space to Cont on nSD-Activ = 0.09") +-> do + Cont_PAct.ext_fast_enclosed(pSD, 0.09.um, polygon_output: true) +end.().output("Cnt.g2", "Min. pSD overlap of Cont on pSD-Activ = 0.09") -> do Cont_Act.ext_not(SVaricap).ext_fast_separation(GP_Nsram, 0.11.um) end.().output("Cnt.f", "Min. Cont on Activ space to GatPoly = 0.11") diff --git a/ihp-sg13g2/libs.tech/klayout/tech/drc/tests/Cont/sg13g2_test_Cont.gds.gz b/ihp-sg13g2/libs.tech/klayout/tech/drc/tests/Cont/sg13g2_test_Cont.gds.gz new file mode 100644 index 00000000..998668fb Binary files /dev/null and b/ihp-sg13g2/libs.tech/klayout/tech/drc/tests/Cont/sg13g2_test_Cont.gds.gz differ diff --git a/ihp-sg13g2/libs.tech/klayout/tech/drc/tests/Cont/test_Cont.py b/ihp-sg13g2/libs.tech/klayout/tech/drc/tests/Cont/test_Cont.py new file mode 100644 index 00000000..14220be7 --- /dev/null +++ b/ihp-sg13g2/libs.tech/klayout/tech/drc/tests/Cont/test_Cont.py @@ -0,0 +1,44 @@ +"""Module to test specific layout rules.""" +# pylint: disable=invalid-name, missing-function-docstring,redefined-outer-name +import pytest + + +@pytest.fixture(scope="module") +def drc_test_name(): + return "Cont" + + +@pytest.fixture(scope="module") +def drc_test_flags(): + return {'density': False} + + +testdata = [ + ("Cnt.a", 2), + ("Cnt.b", 1), + ("Cnt.c", 2), + ("Cnt.g1", 0), # should be 1 but rule doesn't work + ("Cnt.g2", 1), + ("Cnt.f", 1), + ("Cnt.g", 1), + ("Cnt.h", 1), + ("Cnt.j", 1), + ("M1.c", 1), + ("M1.c1", 1), +] + +@pytest.fixture(scope="module") +def get_valid_tests(): + return [t for t, _ in testdata] + + +@pytest.mark.parametrize("rule,value", testdata) +def test_rule_Cnt(rule, value, sg13g2_drc_maximal): + print(f"Test Rule {rule}") + assert sg13g2_drc_maximal[rule] == value + +def test_invalid_issues_Cnt(sg13g2_drc_maximal_failed_tests, get_valid_tests): + print(get_valid_tests) + print(sg13g2_drc_maximal_failed_tests) + invalid_issues = set(sg13g2_drc_maximal_failed_tests) - set(get_valid_tests) + assert len(invalid_issues) == 0 diff --git a/ihp-sg13g2/libs.tech/klayout/tech/drc/tests/conftest.py b/ihp-sg13g2/libs.tech/klayout/tech/drc/tests/conftest.py new file mode 100644 index 00000000..6ac3c0d6 --- /dev/null +++ b/ihp-sg13g2/libs.tech/klayout/tech/drc/tests/conftest.py @@ -0,0 +1,31 @@ +"""Module with common fixtures.""" +# pylint: disable=redefined-outer-name +import pathlib +import re +import subprocess +from typing import cast +import pytest + +RULE_REGEX = r"Rule (?P(\w|\.)+): (?P\d+) error\(s\)" + +@pytest.fixture(scope="module") +def sg13g2_drc_maximal(drc_test_name, drc_test_flags): + """Returns a dict of rule name and number of violations.""" + cur_dir = pathlib.Path().resolve() / "ihp-sg13g2/libs.tech/klayout/tech/" + cmd = f"klayout -n sg13g2 -b -r {cur_dir}/drc/sg13g2_maximal.lydrc " \ + f"-rd cell=sg13g2_test_{drc_test_name} " \ + f"{cur_dir}/drc/tests/{drc_test_name}/sg13g2_test_{drc_test_name}.gds.gz" + if not drc_test_flags.get('density', True): + cmd += " -rd density=false" + result = subprocess.run(cmd, shell=True, capture_output=True, check=True) + + pattern = re.compile(RULE_REGEX) + matches = (pattern.match(r) for r in result.stdout.decode('utf-8').split('\n')) + matches_ = (cast(re.Match, x) for x in matches if x is not None) + return {x.groupdict()['rule']: int(x.groupdict()['issues']) for x in matches_} + + +@pytest.fixture(scope="module") +def sg13g2_drc_maximal_failed_tests(sg13g2_drc_maximal): + """Returns a list of all failed checks.""" + return [k for k,v in sg13g2_drc_maximal.items() if v > 0] diff --git a/requirements-test.txt b/requirements-test.txt new file mode 100644 index 00000000..2ebd49b9 --- /dev/null +++ b/requirements-test.txt @@ -0,0 +1,4 @@ +iniconfig==2.0.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.3 diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..f2673344 --- /dev/null +++ b/tox.ini @@ -0,0 +1,19 @@ +[tox] +envlist = py312-test +minversion = v4.0 + +[testenv:{py39,py310,py311,py312}-test] +basepython = + py39: python3.9 + py310: python3.10 + py311: python3.11 + py312: python3.12 +setenv = + py39: PREFIX = py39- + py310: PREFIX = py310- + py311: PREFIX = py311- + py312: PREFIX = py312- +deps = + test: -rrequirements-test.txt +commands = + test: pytest ihp-sg13g2/libs.tech/klayout/tech/drc/tests