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)
|
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("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
|
# parse pins from template def
|
||||||
# def syntax: https://coriolis.lip6.fr/doc/lefdef/lefdefref/DEFSyntax.html
|
# 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+) ;")
|
layer_re = re.compile(r"LAYER (\S+) ;")
|
||||||
rect_re = re.compile(r"RECT (\S+) (\S+) (\S+) (\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
|
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()
|
pins_seen = set()
|
||||||
current_pin = None
|
current_pin = None
|
||||||
pin_rects = 0
|
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))
|
ports.append((layer, lx, by, rx, ty))
|
||||||
lef_ports[current_pin] = ports
|
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:
|
for current_pin in def_pins:
|
||||||
if current_pin not in lef_ports:
|
if current_pin not in lef_ports:
|
||||||
logging.error(f"Pin {current_pin} not found in {lef}")
|
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
|
lef_errors += 1
|
||||||
|
|
||||||
pin_widths = {}
|
for current_pin in power_pins:
|
||||||
for current_pin in ("VPWR", "VGND"):
|
|
||||||
if current_pin not in lef_ports:
|
if current_pin not in lef_ports:
|
||||||
logging.error(f"Pin {current_pin} not found in {lef}")
|
logging.error(f"Pin {current_pin} not found in {lef}")
|
||||||
lef_errors += 1
|
lef_errors += 1
|
||||||
else:
|
else:
|
||||||
for layer, lx, by, rx, ty in lef_ports[current_pin]:
|
for layer, lx, by, rx, ty in lef_ports[current_pin]:
|
||||||
width, height = rx - lx, ty - by
|
width = rx - lx
|
||||||
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":
|
if layer != "met4":
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Port {current_pin} has wrong layer in {lef}: {layer} != met4"
|
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"
|
f"Port {current_pin} has too small width in {lef}: {width/1000} < 1.2 um"
|
||||||
)
|
)
|
||||||
lef_errors += 1
|
lef_errors += 1
|
||||||
if width > 2000:
|
if by > 10000:
|
||||||
logging.error(
|
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
|
lef_errors += 1
|
||||||
if height < die_height * 0.95:
|
if die_height - ty > 10000:
|
||||||
logging.error(
|
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
|
lef_errors += 1
|
||||||
if lx < 0 or rx > die_width or by < 0 or ty > die_height:
|
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
|
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
|
# check for overlapping pins
|
||||||
|
|
||||||
for (pin1, rects1), (pin2, rects2) in combinations(sorted(lef_ports.items()), 2):
|
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))
|
logging.info("info.yaml data:" + str(yaml_data))
|
||||||
tiles = yaml_data.get("project", {}).get("tiles", "1x1")
|
tiles = yaml_data.get("project", {}).get("tiles", "1x1")
|
||||||
is_analog = yaml_data.get("project", {}).get("analog_pins", 0) > 0
|
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:
|
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:
|
else:
|
||||||
template_def = f"../def/tt_block_{tiles}_pg.def"
|
template_def = f"../def/tt_block_{tiles}_pg.def"
|
||||||
logging.info(f"using def template {template_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 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)],
|
[
|
||||||
|
"Pin check",
|
||||||
|
lambda: pin_check(args.gds, lef, template_def, top_module, uses_3v3),
|
||||||
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
testsuite = ET.Element("testsuite", name="Tiny Tapeout Prechecks")
|
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)
|
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(
|
def generate_analog_example(
|
||||||
tcl_file: str,
|
tcl_file: str,
|
||||||
gds_file: str,
|
gds_file: str,
|
||||||
lef_file: str,
|
lef_file: str,
|
||||||
toplevel: str,
|
toplevel: str,
|
||||||
vpwr_layer: str,
|
extra_ports: list[CompoundPort],
|
||||||
vpwr_boxes: str,
|
|
||||||
vgnd_layer: str,
|
|
||||||
vgnd_boxes: str,
|
|
||||||
):
|
):
|
||||||
with open(tcl_file, "w") as f:
|
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"""
|
f"""
|
||||||
def read ../def/analog/tt_analog_1x2.def
|
port {port.name} makeall n
|
||||||
cellname rename tt_um_template {toplevel}
|
port {port.name} use {port.port_use}
|
||||||
|
port {port.name} class {port.port_class}
|
||||||
# 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 conn n s e w
|
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(
|
magic = subprocess.run(
|
||||||
@@ -179,10 +198,30 @@ def gds_lef_analog_example(tmp_path_factory: pytest.TempPathFactory):
|
|||||||
str(gds_file),
|
str(gds_file),
|
||||||
str(lef_file),
|
str(lef_file),
|
||||||
"TEST_analog_example",
|
"TEST_analog_example",
|
||||||
"met4",
|
[
|
||||||
"{100 500 250 22076}",
|
SimplePort("VDPWR", "met4", (100, 500), (250, 22076)),
|
||||||
"met4",
|
SimplePort("VGND", "met4", (4900, 500), (5050, 22076)),
|
||||||
"{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)
|
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(gds_file),
|
||||||
str(lef_file),
|
str(lef_file),
|
||||||
"TEST_analog_wrong_vgnd",
|
"TEST_analog_wrong_vgnd",
|
||||||
"met4",
|
[
|
||||||
"{100 500 250 22076}",
|
SimplePort("VDPWR", "met4", (100, 500), (250, 22076)),
|
||||||
"met3",
|
SimplePort("VGND", "met3", (4900, 500), (5250, 12076)),
|
||||||
"{4900 500 5250 12076}",
|
],
|
||||||
)
|
)
|
||||||
return str(gds_file), str(lef_file)
|
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(gds_file),
|
||||||
str(lef_file),
|
str(lef_file),
|
||||||
"TEST_analog_overlapping_vgnd",
|
"TEST_analog_overlapping_vgnd",
|
||||||
"met4",
|
[
|
||||||
"{100 500 250 22076}",
|
SimplePort("VDPWR", "met4", (100, 500), (250, 22076)),
|
||||||
"met4",
|
SimplePort("VGND", "met4", (3000, 20), (3200, 22504)),
|
||||||
"{349 20 549 22504}",
|
],
|
||||||
)
|
)
|
||||||
return str(gds_file), str(lef_file)
|
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")
|
@pytest.fixture(scope="session")
|
||||||
def gds_lef_analog_compound_vgnd(tmp_path_factory: pytest.TempPathFactory):
|
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."""
|
"""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"
|
tcl_file = tmp_path_factory.mktemp("tcl") / "TEST_analog_compound_vgnd.tcl"
|
||||||
gds_file = tmp_path_factory.mktemp("gds") / "TEST_analog_example.gds"
|
gds_file = tmp_path_factory.mktemp("gds") / "TEST_analog_compound_vgnd.gds"
|
||||||
lef_file = tmp_path_factory.mktemp("lef") / "TEST_analog_example.lef"
|
lef_file = tmp_path_factory.mktemp("lef") / "TEST_analog_compound_vgnd.lef"
|
||||||
|
|
||||||
generate_analog_example(
|
generate_analog_example(
|
||||||
str(tcl_file),
|
str(tcl_file),
|
||||||
str(gds_file),
|
str(gds_file),
|
||||||
str(lef_file),
|
str(lef_file),
|
||||||
"TEST_analog_compound_vgnd",
|
"TEST_analog_compound_vgnd",
|
||||||
"met4",
|
[
|
||||||
"{100 500 250 22076}",
|
SimplePort("VDPWR", "met4", (100, 500), (250, 22076)),
|
||||||
"met4",
|
CompoundPort(
|
||||||
"{4900 500 5050 12076} {4900 12000 5050 22076}",
|
"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)
|
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,
|
lef_file,
|
||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_example",
|
"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,
|
lef_file,
|
||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_wrong_vgnd",
|
"TEST_analog_wrong_vgnd",
|
||||||
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -345,6 +424,7 @@ def test_pin_analog_overlapping_vgnd(gds_lef_analog_overlapping_vgnd: tuple[str,
|
|||||||
lef_file,
|
lef_file,
|
||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_overlapping_vgnd",
|
"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,
|
lef_file,
|
||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_compound_vgnd",
|
"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,
|
||||||
|
)
|
||||||
|
|||||||
+22
-7
@@ -187,6 +187,19 @@ class Project:
|
|||||||
os.unlink(json_file)
|
os.unlink(json_file)
|
||||||
|
|
||||||
module_ports = ports["modules"][top]["ports"]
|
module_ports = ports["modules"][top]["ports"]
|
||||||
|
if "VPWR" in module_ports:
|
||||||
|
if "VDPWR" in module_ports:
|
||||||
|
logging.error(
|
||||||
|
f"{self} top module '{top}' uses both VPWR and VDPWR ports"
|
||||||
|
)
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
logging.info(
|
||||||
|
f"{self} top module '{top}' uses VPWR port, substituting VDPWR"
|
||||||
|
)
|
||||||
|
module_ports["VDPWR"] = module_ports["VPWR"]
|
||||||
|
del module_ports["VPWR"]
|
||||||
|
|
||||||
required_ports = [
|
required_ports = [
|
||||||
["input", "clk", 1],
|
["input", "clk", 1],
|
||||||
["input", "ena", 1],
|
["input", "ena", 1],
|
||||||
@@ -197,15 +210,19 @@ class Project:
|
|||||||
["output", "uio_out", 8],
|
["output", "uio_out", 8],
|
||||||
["output", "uo_out", 8],
|
["output", "uo_out", 8],
|
||||||
]
|
]
|
||||||
if self.is_analog_design():
|
if self.info.is_analog:
|
||||||
required_ports += [
|
required_ports += [
|
||||||
["inout", "ua", 8],
|
["inout", "ua", 8],
|
||||||
]
|
]
|
||||||
if include_power_ports:
|
if include_power_ports:
|
||||||
required_ports += [
|
required_ports += [
|
||||||
["input", "VGND", 1],
|
["input", "VGND", 1],
|
||||||
["input", "VPWR", 1],
|
["input", "VDPWR", 1],
|
||||||
]
|
]
|
||||||
|
if self.info.uses_3v3:
|
||||||
|
required_ports += [
|
||||||
|
["input", "VAPWR", 1],
|
||||||
|
]
|
||||||
for direction, port, bits in required_ports:
|
for direction, port, bits in required_ports:
|
||||||
if port not in module_ports:
|
if port not in module_ports:
|
||||||
logging.error(f"{self} port '{port}' missing from top module ('{top}')")
|
logging.error(f"{self} port '{port}' missing from top module ('{top}')")
|
||||||
@@ -248,7 +265,7 @@ class Project:
|
|||||||
def get_project_type_string(self):
|
def get_project_type_string(self):
|
||||||
if self.is_wokwi():
|
if self.is_wokwi():
|
||||||
return f"[Wokwi]({self.get_wokwi_url()})"
|
return f"[Wokwi]({self.get_wokwi_url()})"
|
||||||
elif self.is_analog_design():
|
elif self.info.is_analog:
|
||||||
return "Analog"
|
return "Analog"
|
||||||
else:
|
else:
|
||||||
return "HDL"
|
return "HDL"
|
||||||
@@ -306,9 +323,6 @@ class Project:
|
|||||||
commit_info = self.read_commit_info_json()
|
commit_info = self.read_commit_info_json()
|
||||||
return commit_info["workflow_url"]
|
return commit_info["workflow_url"]
|
||||||
|
|
||||||
def is_analog_design(self) -> bool:
|
|
||||||
return len(self.info.pinout.ua) > 0
|
|
||||||
|
|
||||||
def get_workflow_url(self):
|
def get_workflow_url(self):
|
||||||
GITHUB_SERVER_URL = os.getenv("GITHUB_SERVER_URL")
|
GITHUB_SERVER_URL = os.getenv("GITHUB_SERVER_URL")
|
||||||
GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")
|
GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")
|
||||||
@@ -612,7 +626,8 @@ class Project:
|
|||||||
}
|
}
|
||||||
for i, desc in enumerate(self.info.pinout.ua)
|
for i, desc in enumerate(self.info.pinout.ua)
|
||||||
],
|
],
|
||||||
"is_analog": bool(self.info.pinout.ua),
|
"is_analog": self.info.is_analog,
|
||||||
|
"uses_3v3": self.info.uses_3v3,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,13 @@ class ProjectInfo:
|
|||||||
"Invalid value for 'analog_pins' in 'project' section, must be between 0 and 6"
|
"Invalid value for 'analog_pins' in 'project' section, must be between 0 and 6"
|
||||||
)
|
)
|
||||||
self.analog_pins: int = analog_pins
|
self.analog_pins: int = analog_pins
|
||||||
|
self.is_analog: bool = self.analog_pins > 0
|
||||||
|
|
||||||
|
self.uses_3v3: bool = project_section.get("uses_3v3", False)
|
||||||
|
if self.uses_3v3 and not self.is_analog:
|
||||||
|
raise ProjectYamlError(
|
||||||
|
"Projects with 3v3 power need at least one analog pin"
|
||||||
|
)
|
||||||
|
|
||||||
language = project_section.get("language")
|
language = project_section.get("language")
|
||||||
if language is None:
|
if language is None:
|
||||||
|
|||||||
Reference in New Issue
Block a user