mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
feat(precheck): add support for 3v3 templates
This commit is contained in:
+28
-28
@@ -98,8 +98,13 @@ def parsefp3(value: str):
|
||||
return int(mul)
|
||||
|
||||
|
||||
def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
|
||||
def pin_check(gds: str, lef: str, template_def: str, toplevel: str, uses_3v3: bool):
|
||||
logging.info("Running pin check...")
|
||||
logging.info(f"* gds: {gds}")
|
||||
logging.info(f"* lef: {lef}")
|
||||
logging.info(f"* template_def: {template_def}")
|
||||
logging.info(f"* toplevel: {toplevel}")
|
||||
logging.info(f"* uses_3v3: {uses_3v3}")
|
||||
|
||||
# parse pins from template def
|
||||
# def syntax: https://coriolis.lip6.fr/doc/lefdef/lefdefref/DEFSyntax.html
|
||||
@@ -174,8 +179,13 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
|
||||
layer_re = re.compile(r"LAYER (\S+) ;")
|
||||
rect_re = re.compile(r"RECT (\S+) (\S+) (\S+) (\S+) ;")
|
||||
|
||||
power_pins = ["VGND", "VDPWR"]
|
||||
if uses_3v3:
|
||||
power_pins.append("VAPWR")
|
||||
compat_pins = {"VPWR": "VDPWR"}
|
||||
|
||||
macro_active = False
|
||||
pins_expected = set(def_pins).union({"VPWR", "VGND"})
|
||||
pins_expected = set(def_pins).union(power_pins).union(compat_pins)
|
||||
pins_seen = set()
|
||||
current_pin = None
|
||||
pin_rects = 0
|
||||
@@ -259,6 +269,16 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
|
||||
ports.append((layer, lx, by, rx, ty))
|
||||
lef_ports[current_pin] = ports
|
||||
|
||||
for old_pin, new_pin in compat_pins.items():
|
||||
if old_pin in lef_ports:
|
||||
if new_pin in lef_ports:
|
||||
logging.error(f"Both {old_pin} and {new_pin} ports appear in {lef}")
|
||||
lef_errors += 1
|
||||
else:
|
||||
lef_ports[new_pin] = []
|
||||
lef_ports[new_pin].extend(lef_ports[old_pin])
|
||||
del lef_ports[old_pin]
|
||||
|
||||
for current_pin in def_pins:
|
||||
if current_pin not in lef_ports:
|
||||
logging.error(f"Pin {current_pin} not found in {lef}")
|
||||
@@ -280,21 +300,13 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
|
||||
)
|
||||
lef_errors += 1
|
||||
|
||||
pin_widths = {}
|
||||
for current_pin in ("VPWR", "VGND"):
|
||||
for current_pin in power_pins:
|
||||
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
|
||||
width = rx - lx
|
||||
if layer != "met4":
|
||||
logging.error(
|
||||
f"Port {current_pin} has wrong layer in {lef}: {layer} != met4"
|
||||
@@ -305,14 +317,14 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
|
||||
f"Port {current_pin} has too small width in {lef}: {width/1000} < 1.2 um"
|
||||
)
|
||||
lef_errors += 1
|
||||
if width > 2000:
|
||||
if by > 10000:
|
||||
logging.error(
|
||||
f"Port {current_pin} has too large width in {lef}: {width/1000} > 2.0 um"
|
||||
f"Port {current_pin} is too far from bottom edge of module: {by/1000} > 10 um"
|
||||
)
|
||||
lef_errors += 1
|
||||
if height < die_height * 0.95:
|
||||
if die_height - ty > 10000:
|
||||
logging.error(
|
||||
f"Port {current_pin} has too small height in {lef}: {height/1000} < {die_height*0.95/1000} um"
|
||||
f"Port {current_pin} is too far from top edge of module: {(die_height-ty)/1000} > 10 um"
|
||||
)
|
||||
lef_errors += 1
|
||||
if lx < 0 or rx > die_width or by < 0 or ty > die_height:
|
||||
@@ -321,18 +333,6 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
|
||||
)
|
||||
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 for overlapping pins
|
||||
|
||||
for (pin1, rects1), (pin2, rects2) in combinations(sorted(lef_ports.items()), 2):
|
||||
|
||||
+13
-2
@@ -160,8 +160,16 @@ def main():
|
||||
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
|
||||
uses_3v3 = bool(yaml_data.get("project", {}).get("uses_3v3", False))
|
||||
if uses_3v3 and not is_analog:
|
||||
raise PrecheckFailure(
|
||||
"Projects with 3v3 power need at least one analog pin"
|
||||
)
|
||||
if is_analog:
|
||||
template_def = f"../def/analog/tt_analog_{tiles}.def"
|
||||
if uses_3v3:
|
||||
template_def = f"../def/analog/tt_analog_{tiles}_3v3.def"
|
||||
else:
|
||||
template_def = f"../def/analog/tt_analog_{tiles}.def"
|
||||
else:
|
||||
template_def = f"../def/tt_block_{tiles}_pg.def"
|
||||
logging.info(f"using def template {template_def}")
|
||||
@@ -181,7 +189,10 @@ def main():
|
||||
],
|
||||
["KLayout zero area", lambda: klayout_zero_area(args.gds)],
|
||||
["KLayout Checks", lambda: klayout_checks(args.gds)],
|
||||
["Pin check", lambda: pin_check(args.gds, lef, template_def, top_module)],
|
||||
[
|
||||
"Pin check",
|
||||
lambda: pin_check(args.gds, lef, template_def, top_module, uses_3v3),
|
||||
],
|
||||
]
|
||||
|
||||
testsuite = ET.Element("testsuite", name="Tiny Tapeout Prechecks")
|
||||
|
||||
+174
-52
@@ -107,50 +107,69 @@ def gds_invalid_macro_name(tmp_path_factory: pytest.TempPathFactory):
|
||||
return str(gds_file)
|
||||
|
||||
|
||||
class PortRect:
|
||||
def __init__(
|
||||
self, layer: str, bottom_left: tuple[int, int], top_right: tuple[int, int]
|
||||
):
|
||||
self.layer = layer
|
||||
self.lx, self.by = bottom_left
|
||||
self.rx, self.ty = top_right
|
||||
|
||||
|
||||
class CompoundPort:
|
||||
def __init__(self, name: str, rects: list[PortRect]):
|
||||
self.name = name
|
||||
self.port_use = "ground" if name == "VGND" else "power"
|
||||
self.port_class = "bidirectional"
|
||||
self.rects = rects
|
||||
|
||||
|
||||
class SimplePort(CompoundPort):
|
||||
def __init__(self, name: str, *rect_args: list):
|
||||
super().__init__(name, [PortRect(*rect_args)])
|
||||
|
||||
|
||||
def generate_analog_example(
|
||||
tcl_file: str,
|
||||
gds_file: str,
|
||||
lef_file: str,
|
||||
toplevel: str,
|
||||
vpwr_layer: str,
|
||||
vpwr_boxes: str,
|
||||
vgnd_layer: str,
|
||||
vgnd_boxes: str,
|
||||
extra_ports: list[CompoundPort],
|
||||
):
|
||||
with open(tcl_file, "w") as f:
|
||||
f.write(
|
||||
textwrap.dedent(
|
||||
|
||||
def tcl_append(s):
|
||||
f.write(textwrap.dedent(s))
|
||||
|
||||
tcl_append(
|
||||
f"""
|
||||
def read ../def/analog/tt_analog_1x2.def
|
||||
cellname rename tt_um_template {toplevel}
|
||||
"""
|
||||
)
|
||||
for port in extra_ports:
|
||||
for rect in port.rects:
|
||||
tcl_append(
|
||||
f"""
|
||||
box {rect.lx} {rect.by} {rect.rx} {rect.ty}
|
||||
paint {rect.layer}
|
||||
label {port.name} FreeSans {rect.layer}
|
||||
"""
|
||||
)
|
||||
tcl_append(
|
||||
f"""
|
||||
def read ../def/analog/tt_analog_1x2.def
|
||||
cellname rename tt_um_template {toplevel}
|
||||
|
||||
# VPWR
|
||||
foreach vpwr_box {{ {vpwr_boxes} }} {{
|
||||
box {{*}}$vpwr_box
|
||||
paint {vpwr_layer}
|
||||
label VPWR FreeSans {vpwr_layer}
|
||||
}}
|
||||
port VPWR makeall n
|
||||
port VPWR use power
|
||||
port VPWR class bidirectional
|
||||
port {port.name} makeall n
|
||||
port {port.name} use {port.port_use}
|
||||
port {port.name} class {port.port_class}
|
||||
port conn n s e w
|
||||
|
||||
# VGND
|
||||
foreach vgnd_box {{ {vgnd_boxes} }} {{
|
||||
box {{*}}$vgnd_box
|
||||
paint {vgnd_layer}
|
||||
label VGND FreeSans {vgnd_layer}
|
||||
}}
|
||||
port VGND makeall n
|
||||
port VGND use ground
|
||||
port VGND class bidirectional
|
||||
port conn n s e w
|
||||
|
||||
# Export
|
||||
gds write {gds_file}
|
||||
lef write {lef_file}
|
||||
"""
|
||||
)
|
||||
tcl_append(
|
||||
f"""
|
||||
# Export
|
||||
gds write {gds_file}
|
||||
lef write {lef_file}
|
||||
"""
|
||||
)
|
||||
|
||||
magic = subprocess.run(
|
||||
@@ -179,10 +198,30 @@ def gds_lef_analog_example(tmp_path_factory: pytest.TempPathFactory):
|
||||
str(gds_file),
|
||||
str(lef_file),
|
||||
"TEST_analog_example",
|
||||
"met4",
|
||||
"{100 500 250 22076}",
|
||||
"met4",
|
||||
"{4900 500 5050 22076}",
|
||||
[
|
||||
SimplePort("VDPWR", "met4", (100, 500), (250, 22076)),
|
||||
SimplePort("VGND", "met4", (4900, 500), (5050, 22076)),
|
||||
],
|
||||
)
|
||||
return str(gds_file), str(lef_file)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def gds_lef_analog_power_compat(tmp_path_factory: pytest.TempPathFactory):
|
||||
"""Creates a GDS and LEF using the 1x2 analog template, with VPWR instead of VDPWR."""
|
||||
tcl_file = tmp_path_factory.mktemp("tcl") / "TEST_analog_power_compat.tcl"
|
||||
gds_file = tmp_path_factory.mktemp("gds") / "TEST_analog_power_compat.gds"
|
||||
lef_file = tmp_path_factory.mktemp("lef") / "TEST_analog_power_compat.lef"
|
||||
|
||||
generate_analog_example(
|
||||
str(tcl_file),
|
||||
str(gds_file),
|
||||
str(lef_file),
|
||||
"TEST_analog_power_compat",
|
||||
[
|
||||
SimplePort("VPWR", "met4", (100, 500), (250, 22076)),
|
||||
SimplePort("VGND", "met4", (4900, 500), (5050, 22076)),
|
||||
],
|
||||
)
|
||||
return str(gds_file), str(lef_file)
|
||||
|
||||
@@ -199,10 +238,10 @@ def gds_lef_analog_wrong_vgnd(tmp_path_factory: pytest.TempPathFactory):
|
||||
str(gds_file),
|
||||
str(lef_file),
|
||||
"TEST_analog_wrong_vgnd",
|
||||
"met4",
|
||||
"{100 500 250 22076}",
|
||||
"met3",
|
||||
"{4900 500 5250 12076}",
|
||||
[
|
||||
SimplePort("VDPWR", "met4", (100, 500), (250, 22076)),
|
||||
SimplePort("VGND", "met3", (4900, 500), (5250, 12076)),
|
||||
],
|
||||
)
|
||||
return str(gds_file), str(lef_file)
|
||||
|
||||
@@ -219,10 +258,10 @@ def gds_lef_analog_overlapping_vgnd(tmp_path_factory: pytest.TempPathFactory):
|
||||
str(gds_file),
|
||||
str(lef_file),
|
||||
"TEST_analog_overlapping_vgnd",
|
||||
"met4",
|
||||
"{100 500 250 22076}",
|
||||
"met4",
|
||||
"{349 20 549 22504}",
|
||||
[
|
||||
SimplePort("VDPWR", "met4", (100, 500), (250, 22076)),
|
||||
SimplePort("VGND", "met4", (3000, 20), (3200, 22504)),
|
||||
],
|
||||
)
|
||||
return str(gds_file), str(lef_file)
|
||||
|
||||
@@ -230,19 +269,46 @@ def gds_lef_analog_overlapping_vgnd(tmp_path_factory: pytest.TempPathFactory):
|
||||
@pytest.fixture(scope="session")
|
||||
def gds_lef_analog_compound_vgnd(tmp_path_factory: pytest.TempPathFactory):
|
||||
"""Creates a GDS and LEF using the 1x2 analog template, with VGND consisting of two rectangles."""
|
||||
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"
|
||||
tcl_file = tmp_path_factory.mktemp("tcl") / "TEST_analog_compound_vgnd.tcl"
|
||||
gds_file = tmp_path_factory.mktemp("gds") / "TEST_analog_compound_vgnd.gds"
|
||||
lef_file = tmp_path_factory.mktemp("lef") / "TEST_analog_compound_vgnd.lef"
|
||||
|
||||
generate_analog_example(
|
||||
str(tcl_file),
|
||||
str(gds_file),
|
||||
str(lef_file),
|
||||
"TEST_analog_compound_vgnd",
|
||||
"met4",
|
||||
"{100 500 250 22076}",
|
||||
"met4",
|
||||
"{4900 500 5050 12076} {4900 12000 5050 22076}",
|
||||
[
|
||||
SimplePort("VDPWR", "met4", (100, 500), (250, 22076)),
|
||||
CompoundPort(
|
||||
"VGND",
|
||||
[
|
||||
PortRect("met4", (4900, 500), (5050, 12076)),
|
||||
PortRect("met4", (4900, 12000), (5050, 22076)),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
return str(gds_file), str(lef_file)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def gds_lef_analog_example_3v3(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_3v3.tcl"
|
||||
gds_file = tmp_path_factory.mktemp("gds") / "TEST_analog_example_3v3.gds"
|
||||
lef_file = tmp_path_factory.mktemp("lef") / "TEST_analog_example_3v3.lef"
|
||||
|
||||
generate_analog_example(
|
||||
str(tcl_file),
|
||||
str(gds_file),
|
||||
str(lef_file),
|
||||
"TEST_analog_example_3v3",
|
||||
[
|
||||
SimplePort("VDPWR", "met4", (100, 500), (250, 22076)),
|
||||
SimplePort("VAPWR", "met4", (2500, 500), (2650, 22076)),
|
||||
SimplePort("VGND", "met4", (4900, 500), (5050, 22076)),
|
||||
],
|
||||
)
|
||||
return str(gds_file), str(lef_file)
|
||||
|
||||
@@ -317,6 +383,18 @@ def test_pin_analog_example(gds_lef_analog_example: tuple[str, str]):
|
||||
lef_file,
|
||||
"../def/analog/tt_analog_1x2.def",
|
||||
"TEST_analog_example",
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
def test_pin_analog_power_compat(gds_lef_analog_power_compat: tuple[str, str]):
|
||||
gds_file, lef_file = gds_lef_analog_power_compat
|
||||
precheck.pin_check(
|
||||
gds_file,
|
||||
lef_file,
|
||||
"../def/analog/tt_analog_1x2.def",
|
||||
"TEST_analog_power_compat",
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
@@ -331,6 +409,7 @@ def test_pin_analog_wrong_vgnd(gds_lef_analog_wrong_vgnd: tuple[str, str]):
|
||||
lef_file,
|
||||
"../def/analog/tt_analog_1x2.def",
|
||||
"TEST_analog_wrong_vgnd",
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
@@ -345,6 +424,7 @@ def test_pin_analog_overlapping_vgnd(gds_lef_analog_overlapping_vgnd: tuple[str,
|
||||
lef_file,
|
||||
"../def/analog/tt_analog_1x2.def",
|
||||
"TEST_analog_overlapping_vgnd",
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
@@ -355,4 +435,46 @@ def test_pin_analog_compound_vgnd(gds_lef_analog_compound_vgnd: tuple[str, str])
|
||||
lef_file,
|
||||
"../def/analog/tt_analog_1x2.def",
|
||||
"TEST_analog_compound_vgnd",
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
def test_pin_analog_example_3v3(gds_lef_analog_example_3v3: tuple[str, str]):
|
||||
gds_file, lef_file = gds_lef_analog_example_3v3
|
||||
precheck.pin_check(
|
||||
gds_file,
|
||||
lef_file,
|
||||
"../def/analog/tt_analog_1x2.def",
|
||||
"TEST_analog_example_3v3",
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
def test_pin_analog_3v3_mismatch1(gds_lef_analog_example: tuple[str, str]):
|
||||
with pytest.raises(
|
||||
precheck.PrecheckFailure,
|
||||
match="Some ports are missing or have wrong dimensions",
|
||||
):
|
||||
gds_file, lef_file = gds_lef_analog_example
|
||||
precheck.pin_check(
|
||||
gds_file,
|
||||
lef_file,
|
||||
"../def/analog/tt_analog_1x2.def",
|
||||
"TEST_analog_example",
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
def test_pin_analog_3v3_mismatch2(gds_lef_analog_example_3v3: tuple[str, str]):
|
||||
with pytest.raises(
|
||||
precheck.PrecheckFailure,
|
||||
match="Some ports are missing or have wrong dimensions",
|
||||
):
|
||||
gds_file, lef_file = gds_lef_analog_example_3v3
|
||||
precheck.pin_check(
|
||||
gds_file,
|
||||
lef_file,
|
||||
"../def/analog/tt_analog_1x2.def",
|
||||
"TEST_analog_example_3v3",
|
||||
False,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user