From 12f61660abf6fb477da80c5e81049acab5963c46 Mon Sep 17 00:00:00 2001 From: htfab Date: Thu, 12 Dec 2024 03:23:37 +0100 Subject: [PATCH 1/2] feat(precheck): add support for .gds.br compressed layouts --- precheck/precheck.py | 45 ++++++++++++++++++++++++++++----------- precheck/pyproject.toml | 3 ++- precheck/requirements.txt | 4 +++- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/precheck/precheck.py b/precheck/precheck.py index e93b5d6..3db36a2 100644 --- a/precheck/precheck.py +++ b/precheck/precheck.py @@ -3,10 +3,12 @@ import logging import os import subprocess +import tempfile import time import traceback import xml.etree.ElementTree as ET +import brotli import gdstk import klayout.db as pya import klayout.rdb as rdb @@ -90,14 +92,13 @@ def klayout_zero_area(gds: str): return klayout_drc(gds, "zero_area", "zeroarea.rb.drc") -def klayout_checks(gds: str): +def klayout_checks(gds: str, expected_name: str): layout = pya.Layout() layout.read(gds) layers = parse_lyp_layers(LYP_FILE) logging.info("Running top macro name check...") top_cell = layout.top_cell() - expected_name = os.path.splitext(os.path.basename(gds))[0] if top_cell.name != expected_name: raise PrecheckFailure( f"Top macro name mismatch: expected {expected_name}, got {top_cell.name}" @@ -137,15 +138,31 @@ def main(): logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") logging.info(f"PDK_ROOT: {PDK_ROOT}") + if args.gds.endswith(".gds"): + gds_stem = args.gds.removesuffix(".gds") + gds_temp = None + gds_file = args.gds + elif args.gds.endswith(".gds.br"): + gds_stem = args.gds.removesuffix(".gds.br") + gds_temp = tempfile.NamedTemporaryFile(suffix=".gds", delete=False) + gds_file = gds_temp.name + logging.info(f"decompressing {args.gds} to {gds_file}") + with open(args.gds, "rb") as f: + data = f.read() + with open(gds_file, "wb") as f: + f.write(brotli.decompress(data)) + else: + raise PrecheckFailure("Layout file extension is neither .gds nor .gds.br") + if args.top_module: top_module = args.top_module else: - top_module = os.path.splitext(os.path.basename(args.gds))[0] + top_module = os.path.basename(gds_stem) if args.lef: lef = args.lef else: - lef = os.path.splitext(args.gds)[0] + ".lef" + lef = gds_stem + ".lef" if args.template_def: template_def = args.template_def @@ -175,23 +192,23 @@ def main(): logging.info(f"using def template {template_def}") checks = [ - ["Magic DRC", lambda: magic_drc(args.gds, top_module)], - ["KLayout FEOL", lambda: klayout_drc(args.gds, "feol")], - ["KLayout BEOL", lambda: klayout_drc(args.gds, "beol")], - ["KLayout offgrid", lambda: klayout_drc(args.gds, "offgrid")], + ["Magic DRC", lambda: magic_drc(gds_file, top_module)], + ["KLayout FEOL", lambda: klayout_drc(gds_file, "feol")], + ["KLayout BEOL", lambda: klayout_drc(gds_file, "beol")], + ["KLayout offgrid", lambda: klayout_drc(gds_file, "offgrid")], [ "KLayout pin label overlapping drawing", lambda: klayout_drc( - args.gds, + gds_file, "pin_label_purposes_overlapping_drawing", "pin_label_purposes_overlapping_drawing.rb.drc", ), ], - ["KLayout zero area", lambda: klayout_zero_area(args.gds)], - ["KLayout Checks", lambda: klayout_checks(args.gds)], + ["KLayout zero area", lambda: klayout_zero_area(gds_file)], + ["KLayout Checks", lambda: klayout_checks(gds_file, top_module)], [ "Pin check", - lambda: pin_check(args.gds, lef, template_def, top_module, uses_3v3), + lambda: pin_check(gds_file, lef, template_def, top_module, uses_3v3), ], ] @@ -226,6 +243,10 @@ def main(): with open(f"{REPORTS_PATH}/results.md", "w") as f: f.write(markdown_table) + if gds_temp is not None: + gds_temp.close() + os.unlink(gds_temp.name) + if error_count > 0: logging.error(f"Precheck failed for {args.gds}! 😭") logging.error(f"See {REPORTS_PATH} for more details") diff --git a/precheck/pyproject.toml b/precheck/pyproject.toml index cf0a928..b09ddc3 100644 --- a/precheck/pyproject.toml +++ b/precheck/pyproject.toml @@ -3,8 +3,9 @@ name = "tt-precheck" requires-python = ">=3.11" dynamic = ["version"] dependencies = [ - "klayout>=0.28.17.post1,<0.29.0", + "brotli", "gdstk", + "klayout>=0.28.17.post1,<0.29.0", "numpy<2", "pytest", "PyYAML", diff --git a/precheck/requirements.txt b/precheck/requirements.txt index 0f9160b..e2cc11c 100644 --- a/precheck/requirements.txt +++ b/precheck/requirements.txt @@ -1,9 +1,11 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # pip-compile # +brotli==1.1.0 + # via tt-precheck (pyproject.toml) gdstk==0.9.52 # via tt-precheck (pyproject.toml) iniconfig==2.0.0 From 6cdef3ceb5fb9824e68805ada5610ed072886280 Mon Sep 17 00:00:00 2001 From: htfab Date: Thu, 12 Dec 2024 04:08:41 +0100 Subject: [PATCH 2/2] fix(precheck): adapt the test suite to changed klayout_checks() signature --- precheck/test_precheck.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/precheck/test_precheck.py b/precheck/test_precheck.py index da983c7..6308069 100644 --- a/precheck/test_precheck.py +++ b/precheck/test_precheck.py @@ -341,14 +341,14 @@ def test_klayout_beol_fail(gds_fail_met1_poly: str): def test_klayout_checks_pass(gds_valid: str): - precheck.klayout_checks(gds_valid) + precheck.klayout_checks(gds_valid, "TEST_valid") def test_klayout_checks_fail_metal5(gds_fail_metal5_poly: str): with pytest.raises( precheck.PrecheckFailure, match=r"Forbidden layer met5\.drawing found in .+" ): - precheck.klayout_checks(gds_fail_metal5_poly) + precheck.klayout_checks(gds_fail_metal5_poly, "TEST_met5_error") def test_klayout_checks_fail_pr_boundary(gds_no_pr_boundary: str): @@ -356,7 +356,7 @@ def test_klayout_checks_fail_pr_boundary(gds_no_pr_boundary: str): precheck.PrecheckFailure, match=r"prBoundary.boundary \(235/4\) layer not found in .+", ): - precheck.klayout_checks(gds_no_pr_boundary) + precheck.klayout_checks(gds_no_pr_boundary, "TEST_no_prboundary") def test_klayout_top_module_name(gds_invalid_macro_name: str): @@ -364,7 +364,7 @@ def test_klayout_top_module_name(gds_invalid_macro_name: str): precheck.PrecheckFailure, match="Top macro name mismatch: expected TEST_invalid_macro_name, got wrong_name", ): - precheck.klayout_checks(gds_invalid_macro_name) + precheck.klayout_checks(gds_invalid_macro_name, "TEST_invalid_macro_name") def test_klayout_zero_area_drc_pass(gds_valid: str):