mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
feat(precheck/gf180mcuD): run KLayout DRC via PDK run_drc.py
- Density related rules skipped (irrelevant for user projects) - Add docopt dependecy (required by the PDK's run_drc.py)
This commit is contained in:
+46
-7
@@ -4,6 +4,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
@@ -73,6 +74,15 @@ def magic_drc(gds: str, toplevel: str):
|
|||||||
raise PrecheckFailure("Magic DRC failed")
|
raise PrecheckFailure("Magic DRC failed")
|
||||||
|
|
||||||
|
|
||||||
|
def check_drc_report(check: str, report_file: str):
|
||||||
|
report = rdb.ReportDatabase("DRC")
|
||||||
|
report.load(report_file)
|
||||||
|
if report.num_items() > 0:
|
||||||
|
raise PrecheckFailure(
|
||||||
|
f"Klayout {check} failed with {report.num_items()} DRC violations"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def klayout_custom_drc(
|
def klayout_custom_drc(
|
||||||
check: str, script_path: str, script_vars: dict[str, str], report_vars: list[str]
|
check: str, script_path: str, script_vars: dict[str, str], report_vars: list[str]
|
||||||
):
|
):
|
||||||
@@ -86,13 +96,7 @@ def klayout_custom_drc(
|
|||||||
if klayout.returncode != 0:
|
if klayout.returncode != 0:
|
||||||
raise PrecheckFailure(f"Klayout {check} failed")
|
raise PrecheckFailure(f"Klayout {check} failed")
|
||||||
|
|
||||||
report = rdb.ReportDatabase("DRC")
|
check_drc_report(check, report_file)
|
||||||
report.load(report_file)
|
|
||||||
|
|
||||||
if report.num_items() > 0:
|
|
||||||
raise PrecheckFailure(
|
|
||||||
f"Klayout {check} failed with {report.num_items()} DRC violations"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def klayout_drc(
|
def klayout_drc(
|
||||||
@@ -135,6 +139,36 @@ def klayout_gf180mcuD_antenna(gds: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def klayout_gf180mcuD_drc(gds: str, top_module: str):
|
||||||
|
logging.info(f"Running klayout gf180mcuD DRC on {gds}")
|
||||||
|
run_dir = f"{REPORTS_PATH}/gf180mcuD_drc"
|
||||||
|
os.makedirs(run_dir, exist_ok=True)
|
||||||
|
report_file = f"{run_dir}/{os.path.splitext(os.path.basename(gds))[0]}_main.lyrdb"
|
||||||
|
if os.path.exists(report_file):
|
||||||
|
os.remove(report_file)
|
||||||
|
|
||||||
|
run_drc = f"{PDK_ROOT}/{PDK_NAME}/libs.tech/klayout/tech/drc/run_drc.py"
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
run_drc,
|
||||||
|
f"--path={gds}",
|
||||||
|
f"--topcell={top_module}",
|
||||||
|
"--variant=D", # gf180mcuD: metal_top=11K, mim_option=B, metal_level=5LM
|
||||||
|
"--run_mode=deep",
|
||||||
|
"--thr=1", # single-threaded to work around a klayout deadlock bug
|
||||||
|
f"--run_dir={run_dir}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# run_drc.py exits non-zero on violations, so we judge the result from the
|
||||||
|
# report database it produces (matching the other klayout checks).
|
||||||
|
if not os.path.exists(report_file):
|
||||||
|
raise PrecheckFailure("Klayout gf180mcuD DRC did not produce a report")
|
||||||
|
|
||||||
|
check_drc_report("gf180mcuD DRC", report_file)
|
||||||
|
|
||||||
|
|
||||||
def klayout_checks(gds: str, expected_name: str, tech: str):
|
def klayout_checks(gds: str, expected_name: str, tech: str):
|
||||||
layout = pya.Layout()
|
layout = pya.Layout()
|
||||||
layout.read(gds)
|
layout.read(gds)
|
||||||
@@ -488,6 +522,11 @@ def main():
|
|||||||
"check": lambda: urpm_nwell_check(gds_file, top_module),
|
"check": lambda: urpm_nwell_check(gds_file, top_module),
|
||||||
"techs": ["sky130A"],
|
"techs": ["sky130A"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "KLayout GF180MCU DRC",
|
||||||
|
"check": lambda: klayout_gf180mcuD_drc(gds_file, top_module),
|
||||||
|
"techs": ["gf180mcuD"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Antenna check",
|
"name": "Antenna check",
|
||||||
"check": lambda: klayout_gf180mcuD_antenna(gds_file),
|
"check": lambda: klayout_gf180mcuD_antenna(gds_file),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ name = "tt-precheck"
|
|||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"docopt",
|
||||||
"gdstk",
|
"gdstk",
|
||||||
"klayout>=0.28.17.post1,<0.29.0",
|
"klayout>=0.28.17.post1,<0.29.0",
|
||||||
"numpy<2",
|
"numpy<2",
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ drc_pin_label_purposes_overlapping_drawing.xml
|
|||||||
drc_zero_area.xml
|
drc_zero_area.xml
|
||||||
drc_nwell_urpm.xml
|
drc_nwell_urpm.xml
|
||||||
drc_sg13g2.xml
|
drc_sg13g2.xml
|
||||||
|
drc_antenna.xml
|
||||||
|
gf180mcuD_drc/
|
||||||
magic_drc.mag
|
magic_drc.mag
|
||||||
magic_drc.txt
|
magic_drc.txt
|
||||||
results.md
|
results.md
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#
|
#
|
||||||
click==8.3.1
|
click==8.3.1
|
||||||
# via yowasp-yosys
|
# via yowasp-yosys
|
||||||
|
docopt==0.6.2
|
||||||
|
# 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
|
||||||
|
|||||||
@@ -609,6 +609,35 @@ def gds_antenna_fail(tmp_path_factory: pytest.TempPathFactory):
|
|||||||
return str(gds_file)
|
return str(gds_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def gds_gf180_drc_pass(tmp_path_factory: pytest.TempPathFactory):
|
||||||
|
"""Creates a GDS without DRC violations (density rules are not run)."""
|
||||||
|
gds_file = tmp_path_factory.mktemp("gds") / "TEST_gf180_drc_pass.gds"
|
||||||
|
layout = pya.Layout()
|
||||||
|
metal1_info = gds_layers["Metal1"]
|
||||||
|
metal1 = layout.layer(metal1_info.layer, metal1_info.data_type)
|
||||||
|
top_cell = layout.create_cell("TEST_gf180_drc_pass")
|
||||||
|
# A single wide Metal1 shape: satisfies min width, area and spacing.
|
||||||
|
top_cell.shapes(metal1).insert(pya.DBox(0, 0, 1, 1))
|
||||||
|
layout.write(str(gds_file))
|
||||||
|
return str(gds_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def gds_gf180_drc_fail(tmp_path_factory: pytest.TempPathFactory):
|
||||||
|
"""Creates a GDS with a Metal1 spacing violation (rule M1.2a)."""
|
||||||
|
gds_file = tmp_path_factory.mktemp("gds") / "TEST_gf180_drc_fail.gds"
|
||||||
|
layout = pya.Layout()
|
||||||
|
metal1_info = gds_layers["Metal1"]
|
||||||
|
metal1 = layout.layer(metal1_info.layer, metal1_info.data_type)
|
||||||
|
top_cell = layout.create_cell("TEST_gf180_drc_fail")
|
||||||
|
# Two wide Metal1 shapes spaced 0.1um apart, below the min spacing rule.
|
||||||
|
top_cell.shapes(metal1).insert(pya.DBox(0, 0, 1, 1))
|
||||||
|
top_cell.shapes(metal1).insert(pya.DBox(1.1, 0, 2.1, 1))
|
||||||
|
layout.write(str(gds_file))
|
||||||
|
return str(gds_file)
|
||||||
|
|
||||||
|
|
||||||
# TESTS
|
# TESTS
|
||||||
|
|
||||||
|
|
||||||
@@ -937,6 +966,20 @@ def test_verilog_syntax_error(verilog_syntax_error: str):
|
|||||||
precheck.verilog_syntax_check(verilog_syntax_error)
|
precheck.verilog_syntax_check(verilog_syntax_error)
|
||||||
|
|
||||||
|
|
||||||
|
@gf180mcuD_only
|
||||||
|
def test_gf180mcuD_drc_pass(gds_gf180_drc_pass: str):
|
||||||
|
precheck.klayout_gf180mcuD_drc(gds_gf180_drc_pass, "TEST_gf180_drc_pass")
|
||||||
|
|
||||||
|
|
||||||
|
@gf180mcuD_only
|
||||||
|
def test_gf180mcuD_drc_fail(gds_gf180_drc_fail: str):
|
||||||
|
with pytest.raises(
|
||||||
|
precheck.PrecheckFailure,
|
||||||
|
match="Klayout gf180mcuD DRC failed with 1 DRC violations",
|
||||||
|
):
|
||||||
|
precheck.klayout_gf180mcuD_drc(gds_gf180_drc_fail, "TEST_gf180_drc_fail")
|
||||||
|
|
||||||
|
|
||||||
@gf180mcuD_only
|
@gf180mcuD_only
|
||||||
def test_antenna_pass(gds_antenna_pass: str):
|
def test_antenna_pass(gds_antenna_pass: str):
|
||||||
precheck.klayout_gf180mcuD_antenna(gds_antenna_pass)
|
precheck.klayout_gf180mcuD_antenna(gds_antenna_pass)
|
||||||
|
|||||||
Reference in New Issue
Block a user