feat(precheck): add support for .gds.br compressed layouts (#87)

* feat(precheck): add support for .gds.br compressed layouts

* fix(precheck): adapt the test suite to changed klayout_checks() signature

---------

Co-authored-by: htfab <mpw@htamas.net>
This commit is contained in:
htfab
2024-12-12 11:33:38 +02:00
committed by GitHub
co-authored by htfab
parent 152a3ea3b4
commit 69b12ddf2c
4 changed files with 42 additions and 18 deletions
+33 -12
View File
@@ -3,10 +3,12 @@ import argparse
import logging import logging
import os import os
import subprocess import subprocess
import tempfile
import time import time
import traceback import traceback
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import brotli
import gdstk import gdstk
import klayout.db as pya import klayout.db as pya
import klayout.rdb as rdb import klayout.rdb as rdb
@@ -90,14 +92,13 @@ def klayout_zero_area(gds: str):
return klayout_drc(gds, "zero_area", "zeroarea.rb.drc") 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 = pya.Layout()
layout.read(gds) layout.read(gds)
layers = parse_lyp_layers(LYP_FILE) layers = parse_lyp_layers(LYP_FILE)
logging.info("Running top macro name check...") logging.info("Running top macro name check...")
top_cell = layout.top_cell() top_cell = layout.top_cell()
expected_name = os.path.splitext(os.path.basename(gds))[0]
if top_cell.name != expected_name: if top_cell.name != expected_name:
raise PrecheckFailure( raise PrecheckFailure(
f"Top macro name mismatch: expected {expected_name}, got {top_cell.name}" 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.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logging.info(f"PDK_ROOT: {PDK_ROOT}") 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: if args.top_module:
top_module = args.top_module top_module = args.top_module
else: else:
top_module = os.path.splitext(os.path.basename(args.gds))[0] top_module = os.path.basename(gds_stem)
if args.lef: if args.lef:
lef = args.lef lef = args.lef
else: else:
lef = os.path.splitext(args.gds)[0] + ".lef" lef = gds_stem + ".lef"
if args.template_def: if args.template_def:
template_def = args.template_def template_def = args.template_def
@@ -175,23 +192,23 @@ def main():
logging.info(f"using def template {template_def}") logging.info(f"using def template {template_def}")
checks = [ checks = [
["Magic DRC", lambda: magic_drc(args.gds, top_module)], ["Magic DRC", lambda: magic_drc(gds_file, top_module)],
["KLayout FEOL", lambda: klayout_drc(args.gds, "feol")], ["KLayout FEOL", lambda: klayout_drc(gds_file, "feol")],
["KLayout BEOL", lambda: klayout_drc(args.gds, "beol")], ["KLayout BEOL", lambda: klayout_drc(gds_file, "beol")],
["KLayout offgrid", lambda: klayout_drc(args.gds, "offgrid")], ["KLayout offgrid", lambda: klayout_drc(gds_file, "offgrid")],
[ [
"KLayout pin label overlapping drawing", "KLayout pin label overlapping drawing",
lambda: klayout_drc( lambda: klayout_drc(
args.gds, gds_file,
"pin_label_purposes_overlapping_drawing", "pin_label_purposes_overlapping_drawing",
"pin_label_purposes_overlapping_drawing.rb.drc", "pin_label_purposes_overlapping_drawing.rb.drc",
), ),
], ],
["KLayout zero area", lambda: klayout_zero_area(args.gds)], ["KLayout zero area", lambda: klayout_zero_area(gds_file)],
["KLayout Checks", lambda: klayout_checks(args.gds)], ["KLayout Checks", lambda: klayout_checks(gds_file, top_module)],
[ [
"Pin check", "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: with open(f"{REPORTS_PATH}/results.md", "w") as f:
f.write(markdown_table) f.write(markdown_table)
if gds_temp is not None:
gds_temp.close()
os.unlink(gds_temp.name)
if error_count > 0: if error_count > 0:
logging.error(f"Precheck failed for {args.gds}! 😭") logging.error(f"Precheck failed for {args.gds}! 😭")
logging.error(f"See {REPORTS_PATH} for more details") logging.error(f"See {REPORTS_PATH} for more details")
+2 -1
View File
@@ -3,8 +3,9 @@ name = "tt-precheck"
requires-python = ">=3.11" requires-python = ">=3.11"
dynamic = ["version"] dynamic = ["version"]
dependencies = [ dependencies = [
"klayout>=0.28.17.post1,<0.29.0", "brotli",
"gdstk", "gdstk",
"klayout>=0.28.17.post1,<0.29.0",
"numpy<2", "numpy<2",
"pytest", "pytest",
"PyYAML", "PyYAML",
+3 -1
View File
@@ -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: # by the following command:
# #
# pip-compile # pip-compile
# #
brotli==1.1.0
# via tt-precheck (pyproject.toml)
gdstk==0.9.52 gdstk==0.9.52
# via tt-precheck (pyproject.toml) # via tt-precheck (pyproject.toml)
iniconfig==2.0.0 iniconfig==2.0.0
+4 -4
View File
@@ -341,14 +341,14 @@ def test_klayout_beol_fail(gds_fail_met1_poly: str):
def test_klayout_checks_pass(gds_valid: 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): def test_klayout_checks_fail_metal5(gds_fail_metal5_poly: str):
with pytest.raises( with pytest.raises(
precheck.PrecheckFailure, match=r"Forbidden layer met5\.drawing found in .+" 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): 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, precheck.PrecheckFailure,
match=r"prBoundary.boundary \(235/4\) layer not found in .+", 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): 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, precheck.PrecheckFailure,
match="Top macro name mismatch: expected TEST_invalid_macro_name, got wrong_name", 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): def test_klayout_zero_area_drc_pass(gds_valid: str):