mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
feat(precheck): add pin checks
Checks for missing or invalid pins, wrong pin dimensions or layers.
This commit is contained in:
@@ -0,0 +1,268 @@
|
|||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
import gdstk
|
||||||
|
from precheck_failure import PrecheckFailure
|
||||||
|
|
||||||
|
|
||||||
|
def parsefp3(value: str):
|
||||||
|
# parse fixed-point numbers with 3 digits after the decimal point
|
||||||
|
# e.g. '20.470' to 20470
|
||||||
|
ip, fp = value.split(".")
|
||||||
|
mul = ip + fp[:3].rjust(3, "0")
|
||||||
|
return int(mul)
|
||||||
|
|
||||||
|
|
||||||
|
def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
|
||||||
|
logging.info("Running pin check...")
|
||||||
|
|
||||||
|
# parse pins from template def
|
||||||
|
|
||||||
|
diearea_re = re.compile(r"DIEAREA \( (\S+) (\S+) \) \( (\S+) (\S+) \) ;")
|
||||||
|
pins_re = re.compile(r"PINS (\d+) ;")
|
||||||
|
pin_re = re.compile(r" *- (\S+) \+ NET (\S+) \+ DIRECTION (\S+) \+ USE (\S+)")
|
||||||
|
layer_re = re.compile(r" *\+ LAYER (\S+) \( (\S+) (\S+) \) \( (\S+) (\S+) \)")
|
||||||
|
placed_re = re.compile(r" *\+ PLACED \( (\S+) (\S+) \) (\S+) ;")
|
||||||
|
|
||||||
|
def_pins = {}
|
||||||
|
die_width = 0
|
||||||
|
die_height = 0
|
||||||
|
|
||||||
|
with open(template_def) as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith("DIEAREA "):
|
||||||
|
match = diearea_re.match(line)
|
||||||
|
lx, by, rx, ty = map(int, match.groups())
|
||||||
|
if (lx, by) != (0, 0):
|
||||||
|
raise PrecheckFailure("Wrong die origin in template DEF")
|
||||||
|
die_width = rx
|
||||||
|
die_height = ty
|
||||||
|
elif line.startswith("PINS "):
|
||||||
|
match = pins_re.match(line)
|
||||||
|
pin_count = int(match.group(1))
|
||||||
|
break
|
||||||
|
|
||||||
|
for i in range(pin_count):
|
||||||
|
line = next(f)
|
||||||
|
match = pin_re.match(line)
|
||||||
|
pin_name, net_name, direction, use = match.groups()
|
||||||
|
if pin_name != net_name:
|
||||||
|
raise PrecheckFailure(
|
||||||
|
"Inconsistent pin name and net name in template DEF"
|
||||||
|
)
|
||||||
|
|
||||||
|
line = next(f)
|
||||||
|
if not line.strip().startswith("+ PORT"):
|
||||||
|
raise PrecheckFailure("Unexpected token in template DEF")
|
||||||
|
|
||||||
|
line = next(f)
|
||||||
|
match = layer_re.match(line)
|
||||||
|
layer, lx, by, rx, ty = match.groups()
|
||||||
|
lx, by, rx, ty = map(int, (lx, by, rx, ty))
|
||||||
|
|
||||||
|
line = next(f)
|
||||||
|
match = placed_re.match(line)
|
||||||
|
ox, oy, direction = match.groups()
|
||||||
|
ox, oy = map(int, (ox, oy))
|
||||||
|
|
||||||
|
if pin_name in def_pins:
|
||||||
|
raise PrecheckFailure("Duplicate pin in template DEF")
|
||||||
|
|
||||||
|
def_pins[pin_name] = (layer, ox + lx, oy + by, ox + rx, oy + ty)
|
||||||
|
|
||||||
|
line = next(f)
|
||||||
|
if not line.startswith("END PINS"):
|
||||||
|
raise PrecheckFailure("Unexpected token in template DEF")
|
||||||
|
|
||||||
|
# parse pins from user lef
|
||||||
|
|
||||||
|
origin_re = re.compile(r"ORIGIN (\S+) (\S+) ;")
|
||||||
|
size_re = re.compile(r"SIZE (\S+) BY (\S+) ;")
|
||||||
|
layer_re = re.compile(r"LAYER (\S+) ;")
|
||||||
|
rect_re = re.compile(r"RECT (\S+) (\S+) (\S+) (\S+) ;")
|
||||||
|
|
||||||
|
macro_active = False
|
||||||
|
pins_expected = set(def_pins).union({"VPWR", "VGND"})
|
||||||
|
pins_seen = set()
|
||||||
|
current_pin = None
|
||||||
|
pin_rects = 0
|
||||||
|
lef_errors = 0
|
||||||
|
lef_ports = {}
|
||||||
|
|
||||||
|
with open(lef) as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("MACRO "):
|
||||||
|
if line == "MACRO " + toplevel:
|
||||||
|
macro_active = True
|
||||||
|
else:
|
||||||
|
macro_active = False
|
||||||
|
elif macro_active:
|
||||||
|
if line.startswith("ORIGIN "):
|
||||||
|
match = origin_re.match(line)
|
||||||
|
lx, by = map(parsefp3, match.groups())
|
||||||
|
if lx != 0 or by != 0:
|
||||||
|
raise PrecheckFailure("Wrong die origin in LEF")
|
||||||
|
elif line.startswith("SIZE "):
|
||||||
|
match = size_re.match(line)
|
||||||
|
rx, ty = map(parsefp3, match.groups())
|
||||||
|
if (rx, ty) != (die_width, die_height):
|
||||||
|
raise PrecheckFailure(
|
||||||
|
f"Inconsistent die area between LEF and template DEF: ({rx}, {ty}) != ({die_width}, {die_height})"
|
||||||
|
)
|
||||||
|
elif line.startswith("PIN "):
|
||||||
|
if current_pin is not None:
|
||||||
|
raise PrecheckFailure("Unexpected token in LEF")
|
||||||
|
current_pin = line.removeprefix("PIN ")
|
||||||
|
pins_seen.add(current_pin)
|
||||||
|
if current_pin not in pins_expected:
|
||||||
|
logging.error(f"Unexpected pin {current_pin} in {lef}")
|
||||||
|
lef_errors += 1
|
||||||
|
pin_rects = 0
|
||||||
|
elif line == "PORT":
|
||||||
|
if current_pin is None:
|
||||||
|
raise PrecheckFailure("Unexpected token in LEF")
|
||||||
|
line = next(f).strip()
|
||||||
|
while line.startswith("LAYER "):
|
||||||
|
match = layer_re.match(line)
|
||||||
|
layer = match.group(1)
|
||||||
|
line = next(f).strip()
|
||||||
|
while line.startswith("RECT "):
|
||||||
|
match = rect_re.match(line)
|
||||||
|
lx, by, rx, ty = map(parsefp3, match.groups())
|
||||||
|
pin_rects += 1
|
||||||
|
if current_pin not in lef_ports:
|
||||||
|
lef_ports[current_pin] = []
|
||||||
|
lef_ports[current_pin].append((layer, lx, by, rx, ty))
|
||||||
|
line = next(f).strip()
|
||||||
|
if line != "END":
|
||||||
|
raise PrecheckFailure("Unexpected token in LEF")
|
||||||
|
elif current_pin is not None and line.startswith("END " + current_pin):
|
||||||
|
if pin_rects < 1:
|
||||||
|
logging.error(f"No ports for pin {current_pin} in {lef}")
|
||||||
|
lef_errors += 1
|
||||||
|
current_pin = None
|
||||||
|
|
||||||
|
for current_pin in def_pins:
|
||||||
|
if current_pin not in lef_ports:
|
||||||
|
logging.error(f"Pin {current_pin} not found in {lef}")
|
||||||
|
lef_errors += 1
|
||||||
|
else:
|
||||||
|
if len(lef_ports[current_pin]) > 1:
|
||||||
|
logging.error(f"Too many rectangles for pin {current_pin} in {lef}")
|
||||||
|
lef_errors += 1
|
||||||
|
lef_layer, *lef_rect = lef_ports[current_pin][0]
|
||||||
|
def_layer, *def_rect = def_pins[current_pin]
|
||||||
|
if lef_layer != def_layer:
|
||||||
|
logging.error(
|
||||||
|
f"Port {current_pin} on layer {lef_layer} in {lef} but on layer {def_layer} in {template_def}"
|
||||||
|
)
|
||||||
|
lef_errors += 1
|
||||||
|
elif lef_rect != def_rect:
|
||||||
|
logging.error(
|
||||||
|
f"Port {current_pin} has different dimensions in {lef} and {template_def}"
|
||||||
|
)
|
||||||
|
lef_errors += 1
|
||||||
|
|
||||||
|
pin_widths = {}
|
||||||
|
for current_pin in ("VPWR", "VGND"):
|
||||||
|
if current_pin not in lef_ports:
|
||||||
|
logging.error(f"Pin {current_pin} not found in {lef}")
|
||||||
|
lef_errors += 1
|
||||||
|
else:
|
||||||
|
for layer, lx, by, rx, ty in lef_ports[current_pin]:
|
||||||
|
width, height = rx - lx, ty - by
|
||||||
|
if current_pin in pin_widths:
|
||||||
|
if pin_widths[current_pin] != width:
|
||||||
|
logging.error(
|
||||||
|
f"Multiple {current_pin} rectangles with different widths in {lef}: {pin_widths[current_pin]/1000} != {width/1000} um"
|
||||||
|
)
|
||||||
|
lef_errors += 1
|
||||||
|
pin_widths[current_pin] = width
|
||||||
|
if layer != "met4":
|
||||||
|
logging.error(
|
||||||
|
f"Port {current_pin} has wrong layer in {lef}: {layer} != met4"
|
||||||
|
)
|
||||||
|
lef_errors += 1
|
||||||
|
if width < 1200:
|
||||||
|
logging.error(
|
||||||
|
f"Port {current_pin} has too small width in {lef}: {width/1000} < 1.2 um"
|
||||||
|
)
|
||||||
|
lef_errors += 1
|
||||||
|
if width > 2000:
|
||||||
|
logging.error(
|
||||||
|
f"Port {current_pin} has too large width in {lef}: {width/1000} > 2.0 um"
|
||||||
|
)
|
||||||
|
lef_errors += 1
|
||||||
|
if height < die_height * 0.95:
|
||||||
|
logging.error(
|
||||||
|
f"Port {current_pin} has too small height in {lef}: {height/1000} < {die_height*0.95/1000} um"
|
||||||
|
)
|
||||||
|
lef_errors += 1
|
||||||
|
if lx < 0 or rx > die_width or by < 0 or ty > die_height:
|
||||||
|
logging.error(
|
||||||
|
f"Port {current_pin} not entirely within project area in {lef}"
|
||||||
|
)
|
||||||
|
lef_errors += 1
|
||||||
|
|
||||||
|
if (
|
||||||
|
"VPWR" in pin_widths
|
||||||
|
and "VGND" in pin_widths
|
||||||
|
and pin_widths["VPWR"] != pin_widths["VGND"]
|
||||||
|
):
|
||||||
|
vpwr_width = pin_widths["VPWR"]
|
||||||
|
vgnd_width = pin_widths["VGND"]
|
||||||
|
logging.error(
|
||||||
|
f"VPWR and VGND have different widths in {lef}: {vpwr_width/1000} != {vgnd_width/1000} um"
|
||||||
|
)
|
||||||
|
lef_errors += 1
|
||||||
|
|
||||||
|
# check gds for the ports being present
|
||||||
|
|
||||||
|
lib = gdstk.read_gds(gds)
|
||||||
|
top = [cell for cell in lib.top_level() if cell.name == toplevel]
|
||||||
|
if not top:
|
||||||
|
raise PrecheckFailure("Wrong cell at GDS top-level")
|
||||||
|
top = top[0]
|
||||||
|
|
||||||
|
gds_layers = {
|
||||||
|
"met1.pin": (68, 16),
|
||||||
|
"met2.pin": (69, 16),
|
||||||
|
"met3.pin": (70, 16),
|
||||||
|
"met4.pin": (71, 16),
|
||||||
|
}
|
||||||
|
|
||||||
|
gds_errors = 0
|
||||||
|
for current_pin, lef_rects in sorted(lef_ports.items()):
|
||||||
|
for layer, lx, by, rx, ty in lef_rects:
|
||||||
|
assert layer + ".pin" in gds_layers, "Unexpected port layer in LEF"
|
||||||
|
pin_layer = gds_layers[layer + ".pin"]
|
||||||
|
|
||||||
|
pin_ok = False
|
||||||
|
for poly in top.polygons:
|
||||||
|
poly_layer = (poly.layer, poly.datatype)
|
||||||
|
if poly.contain_all(
|
||||||
|
((lx + 1) / 1000, (by + 1) / 1000),
|
||||||
|
((rx - 1) / 1000, (by + 1) / 1000),
|
||||||
|
((lx + 1) / 1000, (ty - 1) / 1000),
|
||||||
|
((rx - 1) / 1000, (ty - 1) / 1000),
|
||||||
|
):
|
||||||
|
if poly_layer == pin_layer:
|
||||||
|
pin_ok = True
|
||||||
|
|
||||||
|
if not pin_ok:
|
||||||
|
logging.error(
|
||||||
|
f"Port {current_pin} missing from layer {layer}.pin in {gds}"
|
||||||
|
)
|
||||||
|
gds_errors += 1
|
||||||
|
|
||||||
|
if lef_errors > 0 or gds_errors > 0:
|
||||||
|
err_list = []
|
||||||
|
if lef_errors > 0:
|
||||||
|
err_list.append(f"{lef_errors} LEF error" + ("s" if lef_errors > 1 else ""))
|
||||||
|
if gds_errors > 0:
|
||||||
|
err_list.append(f"{gds_errors} GDS error" + ("s" if gds_errors > 1 else ""))
|
||||||
|
err_desc = " and ".join(err_list)
|
||||||
|
raise PrecheckFailure(
|
||||||
|
f"Some ports are missing or have wrong dimensions, see {err_desc} above"
|
||||||
|
)
|
||||||
+26
-4
@@ -10,7 +10,10 @@ import xml.etree.ElementTree as ET
|
|||||||
import gdstk
|
import gdstk
|
||||||
import klayout.db as pya
|
import klayout.db as pya
|
||||||
import klayout.rdb as rdb
|
import klayout.rdb as rdb
|
||||||
|
import yaml
|
||||||
from klayout_tools import parse_lyp_layers
|
from klayout_tools import parse_lyp_layers
|
||||||
|
from pin_check import pin_check
|
||||||
|
from precheck_failure import PrecheckFailure
|
||||||
|
|
||||||
PDK_ROOT = os.getenv("PDK_ROOT")
|
PDK_ROOT = os.getenv("PDK_ROOT")
|
||||||
PDK_NAME = os.getenv("PDK_NAME") or "sky130A"
|
PDK_NAME = os.getenv("PDK_NAME") or "sky130A"
|
||||||
@@ -22,10 +25,6 @@ if not PDK_ROOT:
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
class PrecheckFailure(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def has_sky130_devices(gds: str):
|
def has_sky130_devices(gds: str):
|
||||||
for cell_name in gdstk.read_rawcells(gds):
|
for cell_name in gdstk.read_rawcells(gds):
|
||||||
if cell_name.startswith("sky130_fd_"):
|
if cell_name.startswith("sky130_fd_"):
|
||||||
@@ -131,6 +130,8 @@ def klayout_checks(gds: str):
|
|||||||
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("--lef", required=False)
|
||||||
|
parser.add_argument("--template-def", required=False)
|
||||||
parser.add_argument("--top-module", required=False)
|
parser.add_argument("--top-module", 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")
|
||||||
@@ -141,6 +142,26 @@ 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]
|
||||||
|
|
||||||
|
if args.lef:
|
||||||
|
lef = args.lef
|
||||||
|
else:
|
||||||
|
lef = os.path.splitext(args.gds)[0] + ".lef"
|
||||||
|
|
||||||
|
if args.template_def:
|
||||||
|
template_def = args.template_def
|
||||||
|
else:
|
||||||
|
yaml_dir = os.path.dirname(os.path.dirname(args.gds))
|
||||||
|
yaml_file = f"{yaml_dir}/info.yaml"
|
||||||
|
yaml_data = yaml.safe_load(open(yaml_file))
|
||||||
|
logging.info("info.yaml data:" + str(yaml_data))
|
||||||
|
tiles = yaml_data.get("project", {}).get("tiles", "1x1")
|
||||||
|
is_analog = yaml_data.get("project", {}).get("analog_pins", 0) > 0
|
||||||
|
if is_analog:
|
||||||
|
template_def = f"../def/analog/tt_block_{tiles}_pg_ana.def"
|
||||||
|
else:
|
||||||
|
template_def = f"../def/tt_block_{tiles}_pg.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(args.gds, top_module)],
|
||||||
["KLayout FEOL", lambda: klayout_drc(args.gds, "feol")],
|
["KLayout FEOL", lambda: klayout_drc(args.gds, "feol")],
|
||||||
@@ -156,6 +177,7 @@ def main():
|
|||||||
],
|
],
|
||||||
["KLayout zero area", lambda: klayout_zero_area(args.gds)],
|
["KLayout zero area", lambda: klayout_zero_area(args.gds)],
|
||||||
["KLayout Checks", lambda: klayout_checks(args.gds)],
|
["KLayout Checks", lambda: klayout_checks(args.gds)],
|
||||||
|
["Pin check", lambda: pin_check(args.gds, lef, template_def, top_module)],
|
||||||
]
|
]
|
||||||
|
|
||||||
testsuite = ET.Element("testsuite", name="Tiny Tapeout Prechecks")
|
testsuite = ET.Element("testsuite", name="Tiny Tapeout Prechecks")
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
class PrecheckFailure(Exception):
|
||||||
|
pass
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
klayout==0.29.0
|
klayout==0.29.0
|
||||||
gdstk==0.9.50
|
gdstk==0.9.50
|
||||||
pytest==7.4.3
|
pytest==7.4.3
|
||||||
|
PyYAML==6.0.1
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
|
import textwrap
|
||||||
|
|
||||||
import klayout.db as pya
|
import klayout.db as pya
|
||||||
import klayout_tools
|
import klayout_tools
|
||||||
@@ -105,6 +107,102 @@ def gds_invalid_macro_name(tmp_path_factory: pytest.TempPathFactory):
|
|||||||
return str(gds_file)
|
return str(gds_file)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_analog_example(
|
||||||
|
tcl_file: str,
|
||||||
|
gds_file: str,
|
||||||
|
lef_file: str,
|
||||||
|
toplevel: str,
|
||||||
|
vpwr_layer: str,
|
||||||
|
vpwr_box: str,
|
||||||
|
vgnd_layer: str,
|
||||||
|
vgnd_box: str,
|
||||||
|
):
|
||||||
|
with open(tcl_file, "w") as f:
|
||||||
|
f.write(
|
||||||
|
textwrap.dedent(
|
||||||
|
f"""
|
||||||
|
def read ../def/analog/tt_block_1x2_pg_ana.def
|
||||||
|
cellname rename tt_um_template {toplevel}
|
||||||
|
|
||||||
|
# VPWR
|
||||||
|
box {vpwr_box}
|
||||||
|
paint {vpwr_layer}
|
||||||
|
label VPWR FreeSans {vpwr_layer}
|
||||||
|
port VPWR make n
|
||||||
|
port VPWR use power
|
||||||
|
port VPWR class bidirectional
|
||||||
|
port conn n s e w
|
||||||
|
|
||||||
|
# VGND
|
||||||
|
box {vgnd_box}
|
||||||
|
paint {vgnd_layer}
|
||||||
|
label VGND FreeSans {vgnd_layer}
|
||||||
|
port VGND make n
|
||||||
|
port VGND use ground
|
||||||
|
port VGND class bidirectional
|
||||||
|
port conn n s e w
|
||||||
|
|
||||||
|
# Export
|
||||||
|
gds write {gds_file}
|
||||||
|
lef write {lef_file} -pinonly
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
magic = subprocess.run(
|
||||||
|
[
|
||||||
|
"magic",
|
||||||
|
"-noconsole",
|
||||||
|
"-dnull",
|
||||||
|
"-rcfile",
|
||||||
|
f"{PDK_ROOT}/{PDK_NAME}/libs.tech/magic/{PDK_NAME}.magicrc",
|
||||||
|
tcl_file,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert magic.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def gds_lef_analog_example(tmp_path_factory: pytest.TempPathFactory):
|
||||||
|
"""Creates a GDS and LEF using the 1x2 analog template."""
|
||||||
|
tcl_file = tmp_path_factory.mktemp("tcl") / "TEST_analog_example.tcl"
|
||||||
|
gds_file = tmp_path_factory.mktemp("gds") / "TEST_analog_example.gds"
|
||||||
|
lef_file = tmp_path_factory.mktemp("lef") / "TEST_analog_example.lef"
|
||||||
|
|
||||||
|
generate_analog_example(
|
||||||
|
str(tcl_file),
|
||||||
|
str(gds_file),
|
||||||
|
str(lef_file),
|
||||||
|
"TEST_analog_example",
|
||||||
|
"met4",
|
||||||
|
"100 500 250 22076",
|
||||||
|
"met4",
|
||||||
|
"4900 500 5050 22076",
|
||||||
|
)
|
||||||
|
return str(gds_file), str(lef_file)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def gds_lef_analog_wrong_vgnd(tmp_path_factory: pytest.TempPathFactory):
|
||||||
|
"""Creates a GDS and LEF using the 1x2 analog template, with wrong VGND layer & dimensions."""
|
||||||
|
tcl_file = tmp_path_factory.mktemp("tcl") / "TEST_analog_wrong_vgnd.tcl"
|
||||||
|
gds_file = tmp_path_factory.mktemp("gds") / "TEST_analog_wrong_vgnd.gds"
|
||||||
|
lef_file = tmp_path_factory.mktemp("lef") / "TEST_analog_wrong_vgnd.lef"
|
||||||
|
|
||||||
|
generate_analog_example(
|
||||||
|
str(tcl_file),
|
||||||
|
str(gds_file),
|
||||||
|
str(lef_file),
|
||||||
|
"TEST_analog_wrong_vgnd",
|
||||||
|
"met4",
|
||||||
|
"100 500 250 22076",
|
||||||
|
"met3",
|
||||||
|
"4900 500 5250 12076",
|
||||||
|
)
|
||||||
|
return str(gds_file), str(lef_file)
|
||||||
|
|
||||||
|
|
||||||
def test_magic_drc_pass(gds_valid: str):
|
def test_magic_drc_pass(gds_valid: str):
|
||||||
precheck.magic_drc(gds_valid, "TEST_valid")
|
precheck.magic_drc(gds_valid, "TEST_valid")
|
||||||
|
|
||||||
@@ -166,3 +264,27 @@ def test_klayout_zero_area_drc_pass(gds_valid: str):
|
|||||||
def test_klayout_zero_area_drc_fail(gds_zero_area: str):
|
def test_klayout_zero_area_drc_fail(gds_zero_area: str):
|
||||||
with pytest.raises(precheck.PrecheckFailure, match="Klayout zero_area failed"):
|
with pytest.raises(precheck.PrecheckFailure, match="Klayout zero_area failed"):
|
||||||
precheck.klayout_zero_area(gds_zero_area)
|
precheck.klayout_zero_area(gds_zero_area)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pin_analog_example(gds_lef_analog_example: tuple[str, str]):
|
||||||
|
gds_file, lef_file = gds_lef_analog_example
|
||||||
|
precheck.pin_check(
|
||||||
|
gds_file,
|
||||||
|
lef_file,
|
||||||
|
"../def/analog/tt_block_1x2_pg_ana.def",
|
||||||
|
"TEST_analog_example",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pin_analog_wrong_vgnd(gds_lef_analog_wrong_vgnd: tuple[str, str]):
|
||||||
|
with pytest.raises(
|
||||||
|
precheck.PrecheckFailure,
|
||||||
|
match="Some ports are missing or have wrong dimensions",
|
||||||
|
):
|
||||||
|
gds_file, lef_file = gds_lef_analog_wrong_vgnd
|
||||||
|
precheck.pin_check(
|
||||||
|
gds_file,
|
||||||
|
lef_file,
|
||||||
|
"../def/analog/tt_block_1x2_pg_ana.def",
|
||||||
|
"TEST_analog_wrong_vgnd",
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user