mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
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:
+33
-12
@@ -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")
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user