feat(precheck): improve error reporting

This commit is contained in:
Uri Shaked
2023-11-21 01:30:32 +02:00
parent 2c969114d1
commit ed28c2f373
3 changed files with 72 additions and 42 deletions
+55 -23
View File
@@ -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,15 +111,52 @@ 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")
logging.info(f"Precheck passed for {args.gds}! 🎉") 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}! 🎉")
if __name__ == "__main__": if __name__ == "__main__":
+5 -3
View File
@@ -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
View File
@@ -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)