From d1bbd34d956c814af63a463d1af1d4f534b6ccf1 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Sun, 14 Jun 2026 20:23:49 +0300 Subject: [PATCH] feat(precheck/gf180mcuD): update to latest PDK --- .github/workflows/test-precheck-gf180mcu.yaml | 2 +- precheck/default.nix | 11 +++- precheck/precheck.py | 62 ++++++++----------- precheck/pyproject.toml | 1 - precheck/reports/.gitignore | 2 +- precheck/requirements.txt | 2 - precheck/test_precheck.py | 6 +- 7 files changed, 42 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test-precheck-gf180mcu.yaml b/.github/workflows/test-precheck-gf180mcu.yaml index c2fae57..18c9557 100644 --- a/.github/workflows/test-precheck-gf180mcu.yaml +++ b/.github/workflows/test-precheck-gf180mcu.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest env: PDK_ROOT: /home/runner/pdk - PDK_VERSION: ff08c23db8359afce3f134c454e7930586d0641c + PDK_VERSION: e8daeda73ca8f5814dbc0b11d1d05802251a3750 steps: - name: Check out code diff --git a/precheck/default.nix b/precheck/default.nix index 2606bf3..2473058 100644 --- a/precheck/default.nix +++ b/precheck/default.nix @@ -3,9 +3,18 @@ , }: +let + # KLayout 0.30.8 is required by the latest gf180mcu rule-framework DRC deck + # (it uses LayoutToNetlist#copy, which older KLayout lacks). Pinned + # separately so magic-vlsi keeps coming from the base nixpkgs above. + klayoutPkgs = import + (fetchTarball + "https://github.com/NixOS/nixpkgs/archive/ffa10e26ae11d676b2db836259889f1f571cb14f.tar.gz") + { }; +in pkgs.mkShell { buildInputs = [ - pkgs.klayout + klayoutPkgs.klayout pkgs.magic-vlsi ]; } diff --git a/precheck/precheck.py b/precheck/precheck.py index 4e59248..ca15ea2 100644 --- a/precheck/precheck.py +++ b/precheck/precheck.py @@ -4,7 +4,6 @@ import logging import os import re import subprocess -import sys import tempfile import time import traceback @@ -130,44 +129,37 @@ def klayout_sg13g2(gds: str): ) -def klayout_gf180mcuD_antenna(gds: str): - return klayout_drc( - gds, - "antenna", - "antenna.drc", - script_dir=f"{PDK_ROOT}/{PDK_NAME}/libs.tech/klayout/tech/drc/rule_decks", - ) +def klayout_gf180mcuD_rule_deck(gds: str, top_module: str, check: str, decks: str): + # All gf180mcuD DRC runs go through the PDK's unified rule deck + # (gf180mcu.drc); `decks` selects which rule groups to run (see its + # `-rd help=true` output). + logging.info(f"Running klayout {check} on {gds}") + script = f"{PDK_ROOT}/{PDK_NAME}/libs.tech/klayout/tech/drc/gf180mcu.drc" + script_vars = { + "input": gds, + "topcell": top_module, + "variant": "gf180mcuD", # metal_top=11K, mim_option=B, metal_level=5LM + "run_mode": "deep", + "threads": "1", # single-threaded to work around a klayout bug + "decks": decks, + } + klayout_custom_drc(check, script, script_vars, ["report"]) + + +def klayout_gf180mcuD_antenna(gds: str, top_module: str): + # Antenna rules only - they are part of the full deck but get their own + # check, so the full DRC below excludes them to avoid running them twice. + return klayout_gf180mcuD_rule_deck(gds, top_module, "antenna", "antenna") def klayout_gf180mcuD_drc(gds: str, top_module: str): - logging.info(f"Running klayout gf180mcuD DRC on {gds}") - run_dir = f"{REPORTS_PATH}/gf180mcuD_drc" - os.makedirs(run_dir, exist_ok=True) - report_file = f"{run_dir}/{os.path.splitext(os.path.basename(gds))[0]}_main.lyrdb" - if os.path.exists(report_file): - os.remove(report_file) - - run_drc = f"{PDK_ROOT}/{PDK_NAME}/libs.tech/klayout/tech/drc/run_drc.py" - subprocess.run( - [ - sys.executable, - run_drc, - f"--path={gds}", - f"--topcell={top_module}", - "--variant=D", # gf180mcuD: metal_top=11K, mim_option=B, metal_level=5LM - "--run_mode=deep", - "--thr=1", # single-threaded to work around a klayout deadlock bug - f"--run_dir={run_dir}", - ], + # Full rule deck (FEOL + BEOL + connectivity) minus density and antenna. + # Density is excluded because user projects rely on the metal fill that is + # added after precheck to meet it; antenna has its own check above. + return klayout_gf180mcuD_rule_deck( + gds, top_module, "gf180mcuD", "all,-density,-antenna" ) - # run_drc.py exits non-zero on violations, so we judge the result from the - # report database it produces (matching the other klayout checks). - if not os.path.exists(report_file): - raise PrecheckFailure("Klayout gf180mcuD DRC did not produce a report") - - check_drc_report("gf180mcuD DRC", report_file) - def klayout_checks(gds: str, expected_name: str, tech: str): layout = pya.Layout() @@ -529,7 +521,7 @@ def main(): }, { "name": "Antenna check", - "check": lambda: klayout_gf180mcuD_antenna(gds_file), + "check": lambda: klayout_gf180mcuD_antenna(gds_file, top_module), "techs": ["gf180mcuD"], }, { diff --git a/precheck/pyproject.toml b/precheck/pyproject.toml index 0586e3c..9008317 100644 --- a/precheck/pyproject.toml +++ b/precheck/pyproject.toml @@ -3,7 +3,6 @@ name = "tt-precheck" requires-python = ">=3.11" dynamic = ["version"] dependencies = [ - "docopt", "gdstk", "klayout>=0.28.17.post1,<0.29.0", "numpy<2", diff --git a/precheck/reports/.gitignore b/precheck/reports/.gitignore index d120ac5..e791120 100644 --- a/precheck/reports/.gitignore +++ b/precheck/reports/.gitignore @@ -6,7 +6,7 @@ drc_zero_area.xml drc_nwell_urpm.xml drc_sg13g2.xml drc_antenna.xml -gf180mcuD_drc/ +drc_gf180mcuD.xml magic_drc.mag magic_drc.txt results.md diff --git a/precheck/requirements.txt b/precheck/requirements.txt index a99a8a4..5c8faa9 100644 --- a/precheck/requirements.txt +++ b/precheck/requirements.txt @@ -6,8 +6,6 @@ # click==8.3.1 # via yowasp-yosys -docopt==0.6.2 - # via tt-precheck (pyproject.toml) gdstk==0.9.52 # via tt-precheck (pyproject.toml) iniconfig==2.0.0 diff --git a/precheck/test_precheck.py b/precheck/test_precheck.py index 87c6fcc..92be7bf 100644 --- a/precheck/test_precheck.py +++ b/precheck/test_precheck.py @@ -975,14 +975,14 @@ def test_gf180mcuD_drc_pass(gds_gf180_drc_pass: str): def test_gf180mcuD_drc_fail(gds_gf180_drc_fail: str): with pytest.raises( precheck.PrecheckFailure, - match="Klayout gf180mcuD DRC failed with 1 DRC violations", + match="Klayout gf180mcuD failed with 1 DRC violations", ): precheck.klayout_gf180mcuD_drc(gds_gf180_drc_fail, "TEST_gf180_drc_fail") @gf180mcuD_only def test_antenna_pass(gds_antenna_pass: str): - precheck.klayout_gf180mcuD_antenna(gds_antenna_pass) + precheck.klayout_gf180mcuD_antenna(gds_antenna_pass, "TEST_antenna_pass") @gf180mcuD_only @@ -991,4 +991,4 @@ def test_antenna_fail(gds_antenna_fail: str): precheck.PrecheckFailure, match="Klayout antenna failed with 1 DRC violations", ): - precheck.klayout_gf180mcuD_antenna(gds_antenna_fail) + precheck.klayout_gf180mcuD_antenna(gds_antenna_fail, "TEST_antenna_fail")