mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
feat(precheck): improve error reporting
This commit is contained in:
+54
-22
@@ -3,6 +3,9 @@ import argparse
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
import klayout.db as pya
|
import klayout.db as pya
|
||||||
import klayout.rdb as rdb
|
import klayout.rdb as rdb
|
||||||
@@ -18,6 +21,10 @@ if not PDK_ROOT:
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class PrecheckFailure(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def magic_drc(gds: str, toplevel: str):
|
def magic_drc(gds: str, toplevel: str):
|
||||||
logging.info(f"Running magic DRC on {gds} (module={toplevel})")
|
logging.info(f"Running magic DRC on {gds} (module={toplevel})")
|
||||||
|
|
||||||
@@ -38,10 +45,7 @@ def magic_drc(gds: str, toplevel: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if magic.returncode != 0:
|
if magic.returncode != 0:
|
||||||
logging.error("Magic DRC failed")
|
raise PrecheckFailure("Magic DRC failed")
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def klayout_drc(gds: str, check: str):
|
def klayout_drc(gds: str, check: str):
|
||||||
@@ -62,19 +66,15 @@ def klayout_drc(gds: str, check: str):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
if klayout.returncode != 0:
|
if klayout.returncode != 0:
|
||||||
logging.error(f"Klayout {check} failed")
|
raise PrecheckFailure(f"Klayout {check} failed")
|
||||||
return False
|
|
||||||
|
|
||||||
report = rdb.ReportDatabase("DRC")
|
report = rdb.ReportDatabase("DRC")
|
||||||
report.load(report_file)
|
report.load(report_file)
|
||||||
|
|
||||||
if report.num_items() > 0:
|
if report.num_items() > 0:
|
||||||
logging.error(
|
raise PrecheckFailure(
|
||||||
f"Klayout {check} failed with {report.num_items()} DRC violations"
|
f"Klayout {check} failed with {report.num_items()} DRC violations"
|
||||||
)
|
)
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def klayout_checks(gds: str):
|
def klayout_checks(gds: str):
|
||||||
@@ -89,24 +89,19 @@ def klayout_checks(gds: str):
|
|||||||
"met5.label",
|
"met5.label",
|
||||||
]
|
]
|
||||||
|
|
||||||
had_error = False
|
|
||||||
for layer in forbidden_layers:
|
for layer in forbidden_layers:
|
||||||
layer_info = layers[layer]
|
layer_info = layers[layer]
|
||||||
logging.info(f"* Checking {layer_info.name}")
|
logging.info(f"* Checking {layer_info.name}")
|
||||||
layer_index = layout.find_layer(layer_info.layer, layer_info.data_type)
|
layer_index = layout.find_layer(layer_info.layer, layer_info.data_type)
|
||||||
if layer_index is not None:
|
if layer_index is not None:
|
||||||
logging.error(f"Forbidden layer {layer} found in {gds}")
|
raise PrecheckFailure(f"Forbidden layer {layer} found in {gds}")
|
||||||
had_error = True
|
|
||||||
|
|
||||||
if had_error:
|
|
||||||
logging.error("Klayout checks failed")
|
|
||||||
return not had_error
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--gds", required=True)
|
parser.add_argument("--gds", required=True)
|
||||||
parser.add_argument("--top-module", required=False)
|
parser.add_argument("--top-module", required=False)
|
||||||
|
parser.add_argument("--markdown-report", required=False)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
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}")
|
||||||
@@ -116,14 +111,51 @@ def main():
|
|||||||
else:
|
else:
|
||||||
top_module = os.path.splitext(os.path.basename(args.gds))[0]
|
top_module = os.path.splitext(os.path.basename(args.gds))[0]
|
||||||
|
|
||||||
assert magic_drc(args.gds, top_module)
|
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")],
|
||||||
|
["KLayout Checks", lambda: klayout_checks(args.gds)],
|
||||||
|
]
|
||||||
|
|
||||||
assert klayout_drc(args.gds, "feol")
|
testsuite = ET.Element("testsuite", name="Tiny Tapeout Prechecks")
|
||||||
assert klayout_drc(args.gds, "beol")
|
error_count = 0
|
||||||
assert klayout_drc(args.gds, "offgrid")
|
markdown_table = "# Tiny Tapeout Precheck Results\n\n"
|
||||||
|
markdown_table += "| Check | Result |\n|-----------|--------|\n"
|
||||||
|
for [name, check] in checks:
|
||||||
|
start_time = time.time()
|
||||||
|
test_case = ET.SubElement(testsuite, "testcase", name=name)
|
||||||
|
try:
|
||||||
|
check()
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
markdown_table += f"| {name} | ✅ |\n"
|
||||||
|
test_case.set("time", str(round(elapsed_time, 2)))
|
||||||
|
except Exception as e:
|
||||||
|
error_count += 1
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
markdown_table += f"| {name} | Fail: {str(e)} |\n"
|
||||||
|
test_case.set("time", str(round(elapsed_time, 2)))
|
||||||
|
error = ET.SubElement(test_case, "error", message=str(e))
|
||||||
|
error.text = traceback.format_exc()
|
||||||
|
markdown_table += "\n"
|
||||||
|
markdown_table += "In case of failure, please reach out on [discord](https://tinytapeout.com/discord) for assistance."
|
||||||
|
|
||||||
assert klayout_checks(args.gds)
|
testsuites = ET.Element("testsuites")
|
||||||
|
testsuites.append(testsuite)
|
||||||
|
xunit_report = ET.ElementTree(testsuites)
|
||||||
|
ET.indent(xunit_report, space=" ", level=0)
|
||||||
|
xunit_report.write(f"{REPORTS_PATH}/results.xml", encoding="unicode")
|
||||||
|
|
||||||
|
with open(f"{REPORTS_PATH}/results.md", "w") as f:
|
||||||
|
f.write(markdown_table)
|
||||||
|
|
||||||
|
if error_count > 0:
|
||||||
|
logging.error(f"Precheck failed for {args.gds}! 😭")
|
||||||
|
logging.error(f"See {REPORTS_PATH} for more details")
|
||||||
|
logging.error(f"Markdown report:\n{markdown_table}")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
logging.info(f"Precheck passed for {args.gds}! 🎉")
|
logging.info(f"Precheck passed for {args.gds}! 🎉")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
drc_beol.xml
|
||||||
|
drc_feol.xml
|
||||||
|
drc_offgrid.xml
|
||||||
magic_drc.mag
|
magic_drc.mag
|
||||||
magic_drc.txt
|
magic_drc.txt
|
||||||
drc_feol.xml
|
results.md
|
||||||
drc_beol.xml
|
results.xml
|
||||||
drc_offgrid.xml
|
|
||||||
|
|||||||
+12
-16
@@ -68,40 +68,36 @@ def gds_fail_metal5_poly(tmp_path_factory: pytest.TempPathFactory):
|
|||||||
|
|
||||||
|
|
||||||
def test_magic_drc_pass(gds_empty: str):
|
def test_magic_drc_pass(gds_empty: str):
|
||||||
result = precheck.magic_drc(gds_empty, "TEST_empty")
|
precheck.magic_drc(gds_empty, "TEST_empty")
|
||||||
assert result is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_magic_drc_fail(gds_fail_met1_poly: str):
|
def test_magic_drc_fail(gds_fail_met1_poly: str):
|
||||||
result = precheck.magic_drc(gds_fail_met1_poly, "TEST_met1_error")
|
with pytest.raises(precheck.PrecheckFailure):
|
||||||
assert result is False
|
precheck.magic_drc(gds_fail_met1_poly, "TEST_met1_error")
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_feol_pass(gds_empty: str):
|
def test_klayout_feol_pass(gds_empty: str):
|
||||||
result = precheck.klayout_drc(gds_empty, "feol")
|
precheck.klayout_drc(gds_empty, "feol")
|
||||||
assert result is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_feol_fail(gds_fail_nwell_poly: str):
|
def test_klayout_feol_fail(gds_fail_nwell_poly: str):
|
||||||
result = precheck.klayout_drc(gds_fail_nwell_poly, "feol")
|
with pytest.raises(precheck.PrecheckFailure):
|
||||||
assert result is False
|
precheck.klayout_drc(gds_fail_nwell_poly, "feol")
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_beol_pass(gds_empty: str):
|
def test_klayout_beol_pass(gds_empty: str):
|
||||||
result = precheck.klayout_drc(gds_empty, "beol")
|
precheck.klayout_drc(gds_empty, "beol")
|
||||||
assert result is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_beol_fail(gds_fail_met1_poly: str):
|
def test_klayout_beol_fail(gds_fail_met1_poly: str):
|
||||||
result = precheck.klayout_drc(gds_fail_met1_poly, "beol")
|
with pytest.raises(precheck.PrecheckFailure):
|
||||||
assert result is False
|
precheck.klayout_drc(gds_fail_met1_poly, "beol")
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_checks_pass(gds_empty: str):
|
def test_klayout_checks_pass(gds_empty: str):
|
||||||
result = precheck.klayout_checks(gds_empty)
|
precheck.klayout_checks(gds_empty)
|
||||||
assert result is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_checks_fail(gds_fail_metal5_poly: str):
|
def test_klayout_checks_fail(gds_fail_metal5_poly: str):
|
||||||
result = precheck.klayout_checks(gds_fail_metal5_poly)
|
with pytest.raises(precheck.PrecheckFailure):
|
||||||
assert result is False
|
precheck.klayout_checks(gds_fail_metal5_poly)
|
||||||
|
|||||||
Reference in New Issue
Block a user