mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
feat(precheck): initial IHP support
still missing the IHP Klayout DRC deck
This commit is contained in:
@@ -13,7 +13,7 @@ class LayerInfo:
|
|||||||
return f"LayerInfo(name={self.name}, source={self.source}, layer={self.layer}, data_type={self.data_type})"
|
return f"LayerInfo(name={self.name}, source={self.source}, layer={self.layer}, data_type={self.data_type})"
|
||||||
|
|
||||||
|
|
||||||
def parse_lyp_layers(lyp_file: str):
|
def parse_lyp_layers(lyp_file: str, only_valid: bool = True):
|
||||||
with open(lyp_file) as f:
|
with open(lyp_file) as f:
|
||||||
xml_content = f.read()
|
xml_content = f.read()
|
||||||
root = ET.fromstring(xml_content)
|
root = ET.fromstring(xml_content)
|
||||||
@@ -25,7 +25,7 @@ def parse_lyp_layers(lyp_file: str):
|
|||||||
source = properties.find("source")
|
source = properties.find("source")
|
||||||
valid = properties.find("valid")
|
valid = properties.find("valid")
|
||||||
|
|
||||||
if valid is not None and valid.text == "false":
|
if only_valid and valid is not None and valid.text == "false":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from numbers import Real
|
|||||||
|
|
||||||
import gdstk
|
import gdstk
|
||||||
from precheck_failure import PrecheckFailure
|
from precheck_failure import PrecheckFailure
|
||||||
|
from tech_data import power_pins_layer, valid_lef_port_layers
|
||||||
|
|
||||||
|
|
||||||
def canonicalize_rectangles(
|
def canonicalize_rectangles(
|
||||||
@@ -98,7 +99,9 @@ def parsefp3(value: str):
|
|||||||
return int(mul)
|
return int(mul)
|
||||||
|
|
||||||
|
|
||||||
def pin_check(gds: str, lef: str, template_def: str, toplevel: str, uses_3v3: bool):
|
def pin_check(
|
||||||
|
gds: str, lef: str, template_def: str, toplevel: str, uses_3v3: bool, tech: str
|
||||||
|
):
|
||||||
logging.info("Running pin check...")
|
logging.info("Running pin check...")
|
||||||
logging.info(f"* gds: {gds}")
|
logging.info(f"* gds: {gds}")
|
||||||
logging.info(f"* lef: {lef}")
|
logging.info(f"* lef: {lef}")
|
||||||
@@ -307,7 +310,7 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str, uses_3v3: bo
|
|||||||
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 = rx - lx
|
width = rx - lx
|
||||||
if layer != "met4":
|
if layer != power_pins_layer[tech]:
|
||||||
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"
|
||||||
)
|
)
|
||||||
@@ -358,13 +361,7 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str, uses_3v3: bo
|
|||||||
raise PrecheckFailure("Wrong cell at GDS top-level")
|
raise PrecheckFailure("Wrong cell at GDS top-level")
|
||||||
top = top[0]
|
top = top[0]
|
||||||
|
|
||||||
gds_layers = {
|
gds_layers = valid_lef_port_layers[tech]
|
||||||
"met1.pin": (68, 16),
|
|
||||||
"met2.pin": (69, 16),
|
|
||||||
"met3.pin": (70, 16),
|
|
||||||
"met4.pin": (71, 16),
|
|
||||||
}
|
|
||||||
|
|
||||||
gds_layer_lookup = {j: i for i, j in gds_layers.items()}
|
gds_layer_lookup = {j: i for i, j in gds_layers.items()}
|
||||||
polygon_list = {layer: [] for layer in gds_layers}
|
polygon_list = {layer: [] for layer in gds_layers}
|
||||||
for poly in top.polygons:
|
for poly in top.polygons:
|
||||||
|
|||||||
+108
-41
@@ -17,11 +17,18 @@ import yaml
|
|||||||
from klayout_tools import parse_lyp_layers
|
from klayout_tools import parse_lyp_layers
|
||||||
from pin_check import pin_check
|
from pin_check import pin_check
|
||||||
from precheck_failure import PrecheckFailure
|
from precheck_failure import PrecheckFailure
|
||||||
from tech_data import analog_pin_pos, layer_map, valid_layers
|
from tech_data import (
|
||||||
|
analog_pin_pos,
|
||||||
|
forbidden_layers,
|
||||||
|
layer_map,
|
||||||
|
lyp_filename,
|
||||||
|
tech_names,
|
||||||
|
valid_layers,
|
||||||
|
)
|
||||||
|
|
||||||
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"
|
||||||
LYP_FILE = f"{PDK_ROOT}/{PDK_NAME}/libs.tech/klayout/tech/{PDK_NAME}.lyp"
|
LYP_DIR = f"{PDK_ROOT}/{PDK_NAME}/libs.tech/klayout/tech"
|
||||||
REPORTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "reports")
|
REPORTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "reports")
|
||||||
|
|
||||||
if not PDK_ROOT:
|
if not PDK_ROOT:
|
||||||
@@ -36,6 +43,12 @@ def has_sky130_devices(gds: str):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def load_layers(tech: str, only_valid: bool = True):
|
||||||
|
lyp_file = f"{LYP_DIR}/{lyp_filename[tech]}"
|
||||||
|
layers = parse_lyp_layers(lyp_file, only_valid)
|
||||||
|
return layers
|
||||||
|
|
||||||
|
|
||||||
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})")
|
||||||
|
|
||||||
@@ -89,10 +102,10 @@ def klayout_zero_area(gds: str):
|
|||||||
return klayout_drc(gds, "zero_area", "zeroarea.rb.drc")
|
return klayout_drc(gds, "zero_area", "zeroarea.rb.drc")
|
||||||
|
|
||||||
|
|
||||||
def klayout_checks(gds: str, expected_name: str):
|
def klayout_checks(gds: str, expected_name: str, tech: str):
|
||||||
layout = pya.Layout()
|
layout = pya.Layout()
|
||||||
layout.read(gds)
|
layout.read(gds)
|
||||||
layers = parse_lyp_layers(LYP_FILE)
|
layers = load_layers(tech)
|
||||||
|
|
||||||
logging.info("Running top macro name check...")
|
logging.info("Running top macro name check...")
|
||||||
top_cell = layout.top_cell()
|
top_cell = layout.top_cell()
|
||||||
@@ -102,13 +115,7 @@ def klayout_checks(gds: str, expected_name: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
logging.info("Running forbidden layer check...")
|
logging.info("Running forbidden layer check...")
|
||||||
forbidden_layers = [
|
for layer in forbidden_layers[tech]:
|
||||||
"met5.drawing",
|
|
||||||
"met5.pin",
|
|
||||||
"met5.label",
|
|
||||||
]
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -125,7 +132,7 @@ def klayout_checks(gds: str, expected_name: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def boundary_check(gds: str):
|
def boundary_check(gds: str, tech: str):
|
||||||
"""Ensure that there are no shapes outside the project area."""
|
"""Ensure that there are no shapes outside the project area."""
|
||||||
lib = gdstk.read_gds(gds)
|
lib = gdstk.read_gds(gds)
|
||||||
tops = lib.top_level()
|
tops = lib.top_level()
|
||||||
@@ -133,7 +140,10 @@ def boundary_check(gds: str):
|
|||||||
raise PrecheckFailure("GDS top level not unique")
|
raise PrecheckFailure("GDS top level not unique")
|
||||||
top = tops[0]
|
top = tops[0]
|
||||||
boundary = top.copy("test_boundary")
|
boundary = top.copy("test_boundary")
|
||||||
boundary.filter([(235, 4)], False)
|
|
||||||
|
layers = load_layers(tech)
|
||||||
|
layer_info = layers["prBoundary.boundary"]
|
||||||
|
boundary.filter([(layer_info.layer, layer_info.data_type)], False)
|
||||||
if top.bounding_box() != boundary.bounding_box():
|
if top.bounding_box() != boundary.bounding_box():
|
||||||
raise PrecheckFailure("Shapes outside project area")
|
raise PrecheckFailure("Shapes outside project area")
|
||||||
|
|
||||||
@@ -158,9 +168,23 @@ def power_pin_check(verilog: str, lef: str, uses_3v3: bool):
|
|||||||
|
|
||||||
def layer_check(gds: str, tech: str):
|
def layer_check(gds: str, tech: str):
|
||||||
"""Check that there are no invalid layers in the GDS file."""
|
"""Check that there are no invalid layers in the GDS file."""
|
||||||
|
layer_definition = load_layers(tech, only_valid=False)
|
||||||
lib = gdstk.read_gds(gds)
|
lib = gdstk.read_gds(gds)
|
||||||
layers = lib.layers_and_datatypes().union(lib.layers_and_texttypes())
|
valid_layer_list = set(
|
||||||
excess = layers - valid_layers[tech]
|
map(
|
||||||
|
lambda layer_name: (
|
||||||
|
(
|
||||||
|
layer_definition[layer_name].layer,
|
||||||
|
layer_definition[layer_name].data_type,
|
||||||
|
)
|
||||||
|
if type(layer_name) is str
|
||||||
|
else layer_name
|
||||||
|
),
|
||||||
|
valid_layers[tech],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
gds_layers = lib.layers_and_datatypes().union(lib.layers_and_texttypes())
|
||||||
|
excess = gds_layers - valid_layer_list
|
||||||
if excess:
|
if excess:
|
||||||
raise PrecheckFailure(f"Invalid layers in GDS: {excess}")
|
raise PrecheckFailure(f"Invalid layers in GDS: {excess}")
|
||||||
|
|
||||||
@@ -234,11 +258,19 @@ def analog_pin_check(
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
default_tech = PDK_NAME
|
||||||
|
if default_tech not in tech_names:
|
||||||
|
default_tech = tech_names[0]
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--gds", required=True)
|
parser.add_argument("--gds", required=True)
|
||||||
|
parser.add_argument(
|
||||||
|
"--tech", required=False, default=default_tech, choices=tech_names
|
||||||
|
)
|
||||||
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}")
|
||||||
|
logging.info(f"Tech: {args.tech}")
|
||||||
|
|
||||||
if args.gds.endswith(".gds"):
|
if args.gds.endswith(".gds"):
|
||||||
gds_stem = args.gds.removesuffix(".gds")
|
gds_stem = args.gds.removesuffix(".gds")
|
||||||
@@ -256,6 +288,10 @@ def main():
|
|||||||
else:
|
else:
|
||||||
raise PrecheckFailure("Layout file extension is neither .gds nor .gds.br")
|
raise PrecheckFailure("Layout file extension is neither .gds nor .gds.br")
|
||||||
|
|
||||||
|
if args.tech not in tech_names:
|
||||||
|
raise PrecheckFailure(f"Invalid tech: {args.tech}")
|
||||||
|
tech = args.tech
|
||||||
|
|
||||||
yaml_dir = os.path.dirname(args.gds)
|
yaml_dir = os.path.dirname(args.gds)
|
||||||
while not os.path.exists(f"{yaml_dir}/info.yaml"):
|
while not os.path.exists(f"{yaml_dir}/info.yaml"):
|
||||||
yaml_dir = os.path.dirname(yaml_dir)
|
yaml_dir = os.path.dirname(yaml_dir)
|
||||||
@@ -281,55 +317,86 @@ def main():
|
|||||||
template_def = f"../def/analog/tt_analog_{tiles}_3v3.def"
|
template_def = f"../def/analog/tt_analog_{tiles}_3v3.def"
|
||||||
else:
|
else:
|
||||||
template_def = f"../def/analog/tt_analog_{tiles}.def"
|
template_def = f"../def/analog/tt_analog_{tiles}.def"
|
||||||
|
elif tech == "ihp-sg13g2":
|
||||||
|
template_def = f"../ihp/def/tt_block_{tiles}_pgvdd.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}")
|
||||||
|
|
||||||
lef_file = gds_stem + ".lef"
|
lef_file = gds_stem + ".lef"
|
||||||
verilog_file = gds_stem + ".v"
|
verilog_file = gds_stem + ".v"
|
||||||
tech = "sky130"
|
|
||||||
|
|
||||||
checks = [
|
checks = [
|
||||||
["Magic DRC", lambda: magic_drc(gds_file, top_module)],
|
{
|
||||||
["KLayout FEOL", lambda: klayout_drc(gds_file, "feol")],
|
"name": "Magic DRC",
|
||||||
["KLayout BEOL", lambda: klayout_drc(gds_file, "beol")],
|
"check": lambda: magic_drc(gds_file, top_module),
|
||||||
["KLayout offgrid", lambda: klayout_drc(gds_file, "offgrid")],
|
"tech": "sky130",
|
||||||
[
|
},
|
||||||
"KLayout pin label overlapping drawing",
|
{
|
||||||
lambda: klayout_drc(
|
"name": "KLayout FEOL",
|
||||||
|
"check": lambda: klayout_drc(gds_file, "feol"),
|
||||||
|
"tech": "sky130",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "KLayout BEOL",
|
||||||
|
"check": lambda: klayout_drc(gds_file, "beol"),
|
||||||
|
"tech": "sky130",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "KLayout offgrid",
|
||||||
|
"check": lambda: klayout_drc(gds_file, "offgrid"),
|
||||||
|
"tech": "sky130",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "KLayout pin label overlapping drawing",
|
||||||
|
"check": lambda: klayout_drc(
|
||||||
gds_file,
|
gds_file,
|
||||||
"pin_label_purposes_overlapping_drawing",
|
"pin_label_purposes_overlapping_drawing",
|
||||||
"pin_label_purposes_overlapping_drawing.rb.drc",
|
"pin_label_purposes_overlapping_drawing.rb.drc",
|
||||||
),
|
),
|
||||||
],
|
},
|
||||||
["KLayout zero area", lambda: klayout_zero_area(gds_file)],
|
{"name": "KLayout zero area", "check": lambda: klayout_zero_area(gds_file)},
|
||||||
["KLayout Checks", lambda: klayout_checks(gds_file, top_module)],
|
{
|
||||||
[
|
"name": "KLayout Checks",
|
||||||
"Pin check",
|
"check": lambda: klayout_checks(gds_file, top_module, tech),
|
||||||
lambda: pin_check(gds_file, lef_file, template_def, top_module, uses_3v3),
|
},
|
||||||
],
|
{
|
||||||
["Boundary check", lambda: boundary_check(gds_file)],
|
"name": "Pin check",
|
||||||
["Power pin check", lambda: power_pin_check(verilog_file, lef_file, uses_3v3)],
|
"check": lambda: pin_check(
|
||||||
["Layer check", lambda: layer_check(gds_file, tech)],
|
gds_file, lef_file, template_def, top_module, uses_3v3, tech
|
||||||
["Cell name check", lambda: cell_name_check(gds_file)],
|
),
|
||||||
["urpm/nwell check", lambda: urpm_nwell_check(gds_file, top_module)],
|
},
|
||||||
[
|
{"name": "Boundary check", "check": lambda: boundary_check(gds_file, tech)},
|
||||||
"Analog pin check",
|
{
|
||||||
lambda: analog_pin_check(
|
"name": "Power pin check",
|
||||||
|
"check": lambda: power_pin_check(verilog_file, lef_file, uses_3v3),
|
||||||
|
},
|
||||||
|
{"name": "Layer check", "check": lambda: layer_check(gds_file, tech)},
|
||||||
|
{"name": "Cell name check", "check": lambda: cell_name_check(gds_file)},
|
||||||
|
{
|
||||||
|
"name": "urpm/nwell check",
|
||||||
|
"check": lambda: urpm_nwell_check(gds_file, top_module),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Analog pin check",
|
||||||
|
"check": lambda: analog_pin_check(
|
||||||
gds_file, tech, is_analog, uses_3v3, analog_pins, pinout
|
gds_file, tech, is_analog, uses_3v3, analog_pins, pinout
|
||||||
),
|
),
|
||||||
],
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
testsuite = ET.Element("testsuite", name="Tiny Tapeout Prechecks")
|
testsuite = ET.Element("testsuite", name="Tiny Tapeout Prechecks")
|
||||||
error_count = 0
|
error_count = 0
|
||||||
markdown_table = "# Tiny Tapeout Precheck Results\n\n"
|
markdown_table = "# Tiny Tapeout Precheck Results\n\n"
|
||||||
markdown_table += "| Check | Result |\n|-----------|--------|\n"
|
markdown_table += "| Check | Result |\n|-----------|--------|\n"
|
||||||
for [name, check] in checks:
|
for check in checks:
|
||||||
|
name = check["name"]
|
||||||
|
if "tech" in check and check["tech"] != tech:
|
||||||
|
continue
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
test_case = ET.SubElement(testsuite, "testcase", name=name)
|
test_case = ET.SubElement(testsuite, "testcase", name=name)
|
||||||
try:
|
try:
|
||||||
check()
|
check["check"]()
|
||||||
elapsed_time = time.time() - start_time
|
elapsed_time = time.time() - start_time
|
||||||
markdown_table += f"| {name} | ✅ |\n"
|
markdown_table += f"| {name} | ✅ |\n"
|
||||||
test_case.set("time", str(round(elapsed_time, 2)))
|
test_case.set("time", str(round(elapsed_time, 2)))
|
||||||
|
|||||||
+401
-1
@@ -59,6 +59,371 @@ valid_layers_sky130 = {
|
|||||||
}
|
}
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
valid_layers_ihp_sg13g2 = [
|
||||||
|
"Substrate.drawing",
|
||||||
|
"Substrate.text",
|
||||||
|
"Activ.drawing",
|
||||||
|
"Activ.label",
|
||||||
|
"Activ.pin",
|
||||||
|
"Activ.net",
|
||||||
|
"Activ.boundary",
|
||||||
|
"Activ.lvs",
|
||||||
|
"Activ.mask",
|
||||||
|
"Activ.nofill",
|
||||||
|
"Activ.OPC",
|
||||||
|
"Activ.iOPC",
|
||||||
|
"Activ.noqrc",
|
||||||
|
"GatPoly.drawing",
|
||||||
|
"GatPoly.label",
|
||||||
|
"GatPoly.pin",
|
||||||
|
"GatPoly.net",
|
||||||
|
"GatPoly.boundary",
|
||||||
|
"GatPoly.nofill",
|
||||||
|
"GatPoly.OPC",
|
||||||
|
"GatPoly.iOPC",
|
||||||
|
"GatPoly.noqrc",
|
||||||
|
"PolyRes.drawing",
|
||||||
|
"PolyRes.label",
|
||||||
|
"PolyRes.pin",
|
||||||
|
"PolyRes.net",
|
||||||
|
"PolyRes.boundary",
|
||||||
|
"NWell.drawing",
|
||||||
|
"NWell.label",
|
||||||
|
"NWell.pin",
|
||||||
|
"NWell.net",
|
||||||
|
"NWell.boundary",
|
||||||
|
"nBuLay.drawing",
|
||||||
|
"nBuLay.label",
|
||||||
|
"nBuLay.pin",
|
||||||
|
"nBuLay.net",
|
||||||
|
"nBuLay.boundary",
|
||||||
|
"nBuLay.block",
|
||||||
|
"PWell.block",
|
||||||
|
"pSD.drawing",
|
||||||
|
"nSD.drawing",
|
||||||
|
"nSD.block",
|
||||||
|
"SalBlock.drawing",
|
||||||
|
"ThickGateOx.drawing",
|
||||||
|
"Cont.drawing",
|
||||||
|
"Cont.net",
|
||||||
|
"Cont.boundary",
|
||||||
|
"Cont.OPC",
|
||||||
|
"Metal1.drawing",
|
||||||
|
"Metal1.label",
|
||||||
|
"Metal1.pin",
|
||||||
|
"Metal1.net",
|
||||||
|
"Metal1.boundary",
|
||||||
|
"Metal1.mask",
|
||||||
|
"Metal1.nofill",
|
||||||
|
"Metal1.slit",
|
||||||
|
"Metal1.text",
|
||||||
|
"Metal1.OPC",
|
||||||
|
"Metal1.noqrc",
|
||||||
|
"Metal1.res",
|
||||||
|
"Metal1.iprobe",
|
||||||
|
"Metal1.diffprb",
|
||||||
|
"Via1.drawing",
|
||||||
|
"Via1.net",
|
||||||
|
"Via1.boundary",
|
||||||
|
"Metal2.drawing",
|
||||||
|
"Metal2.label",
|
||||||
|
"Metal2.pin",
|
||||||
|
"Metal2.net",
|
||||||
|
"Metal2.boundary",
|
||||||
|
"Metal2.mask",
|
||||||
|
"Metal2.nofill",
|
||||||
|
"Metal2.slit",
|
||||||
|
"Metal2.text",
|
||||||
|
"Metal2.OPC",
|
||||||
|
"Metal2.noqrc",
|
||||||
|
"Metal2.res",
|
||||||
|
"Metal2.iprobe",
|
||||||
|
"Metal2.diffprb",
|
||||||
|
"Via2.drawing",
|
||||||
|
"Via2.net",
|
||||||
|
"Via2.boundary",
|
||||||
|
"Metal3.drawing",
|
||||||
|
"Metal3.label",
|
||||||
|
"Metal3.pin",
|
||||||
|
"Metal3.net",
|
||||||
|
"Metal3.boundary",
|
||||||
|
"Metal3.mask",
|
||||||
|
"Metal3.nofill",
|
||||||
|
"Metal3.slit",
|
||||||
|
"Metal3.text",
|
||||||
|
"Metal3.OPC",
|
||||||
|
"Metal3.noqrc",
|
||||||
|
"Metal3.res",
|
||||||
|
"Metal3.iprobe",
|
||||||
|
"Metal3.diffprb",
|
||||||
|
"MIM.drawing",
|
||||||
|
"MIM.net",
|
||||||
|
"MIM.boundary",
|
||||||
|
"Vmim.drawing",
|
||||||
|
"Via3.drawing",
|
||||||
|
"Via3.net",
|
||||||
|
"Via3.boundary",
|
||||||
|
"Metal4.drawing",
|
||||||
|
"Metal4.label",
|
||||||
|
"Metal4.pin",
|
||||||
|
"Metal4.net",
|
||||||
|
"Metal4.boundary",
|
||||||
|
"Metal4.mask",
|
||||||
|
"Metal4.nofill",
|
||||||
|
"Metal4.slit",
|
||||||
|
"Metal4.text",
|
||||||
|
"Metal4.OPC",
|
||||||
|
"Metal4.noqrc",
|
||||||
|
"Metal4.res",
|
||||||
|
"Metal4.iprobe",
|
||||||
|
"Metal4.diffprb",
|
||||||
|
"Via4.drawing",
|
||||||
|
"Via4.net",
|
||||||
|
"Via4.boundary",
|
||||||
|
"Metal5.drawing",
|
||||||
|
"Metal5.label",
|
||||||
|
"Metal5.pin",
|
||||||
|
"Metal5.net",
|
||||||
|
"Metal5.boundary",
|
||||||
|
"Metal5.mask",
|
||||||
|
"Metal5.nofill",
|
||||||
|
"Metal5.slit",
|
||||||
|
"Metal5.text",
|
||||||
|
"Metal5.OPC",
|
||||||
|
"Metal5.noqrc",
|
||||||
|
"Metal5.res",
|
||||||
|
"Metal5.iprobe",
|
||||||
|
"Metal5.diffprb",
|
||||||
|
"TopVia1.drawing",
|
||||||
|
"TopVia1.net",
|
||||||
|
"TopVia1.boundary",
|
||||||
|
"TopMetal1.drawing",
|
||||||
|
"TopMetal1.label",
|
||||||
|
"TopMetal1.pin",
|
||||||
|
"TopMetal1.net",
|
||||||
|
"TopMetal1.boundary",
|
||||||
|
"TopMetal1.mask",
|
||||||
|
"TopMetal1.nofill",
|
||||||
|
"TopMetal1.slit",
|
||||||
|
"TopMetal1.text",
|
||||||
|
"TopMetal1.noqrc",
|
||||||
|
"TopMetal1.res",
|
||||||
|
"TopMetal1.iprobe",
|
||||||
|
"TopMetal1.diffprb",
|
||||||
|
"TopVia2.drawing",
|
||||||
|
"TopVia2.net",
|
||||||
|
"TopVia2.boundary",
|
||||||
|
"TopMetal2.drawing",
|
||||||
|
"TopMetal2.label",
|
||||||
|
"TopMetal2.pin",
|
||||||
|
"TopMetal2.net",
|
||||||
|
"TopMetal2.boundary",
|
||||||
|
"TopMetal2.mask",
|
||||||
|
"TopMetal2.nofill",
|
||||||
|
"TopMetal2.slit",
|
||||||
|
"TopMetal2.text",
|
||||||
|
"TopMetal2.noqrc",
|
||||||
|
"TopMetal2.res",
|
||||||
|
"TopMetal2.iprobe",
|
||||||
|
"TopMetal2.diffprb",
|
||||||
|
"Passiv.drawing",
|
||||||
|
"Passiv.label",
|
||||||
|
"Passiv.pin",
|
||||||
|
"Passiv.net",
|
||||||
|
"Passiv.boundary",
|
||||||
|
"Passiv.pillar",
|
||||||
|
"Passiv.sbump",
|
||||||
|
"Passiv.pdl",
|
||||||
|
"NoMetFiller.drawing",
|
||||||
|
"ThinFilmRes.drawing",
|
||||||
|
"IND.drawing",
|
||||||
|
"IND.pin",
|
||||||
|
"IND.boundary",
|
||||||
|
"IND.text",
|
||||||
|
"EdgeSeal.drawing",
|
||||||
|
"EdgeSeal.boundary",
|
||||||
|
"dfpad.drawing",
|
||||||
|
"dfpad.pillar",
|
||||||
|
"dfpad.sbump",
|
||||||
|
"TEXT.drawing",
|
||||||
|
"Recog.drawing",
|
||||||
|
"Recog.pin",
|
||||||
|
"Recog.esd",
|
||||||
|
"Recog.diode",
|
||||||
|
"Recog.tsv",
|
||||||
|
"Recog.iprobe",
|
||||||
|
"Recog.diffprb",
|
||||||
|
"Recog.pillar",
|
||||||
|
"Recog.sbump",
|
||||||
|
"Recog.otp",
|
||||||
|
"Recog.pdiode",
|
||||||
|
"Recog.mom",
|
||||||
|
"Recog.pcm",
|
||||||
|
"DigiBnd.drawing",
|
||||||
|
"DigiBnd.drawing0",
|
||||||
|
"RES.drawing",
|
||||||
|
"RES.label",
|
||||||
|
"SRAM.drawing",
|
||||||
|
"SRAM.label",
|
||||||
|
"SRAM.boundary",
|
||||||
|
"TRANS.drawing",
|
||||||
|
"DigiSub.drawing",
|
||||||
|
"HeatTrans.drawing",
|
||||||
|
"HeatRes.drawing",
|
||||||
|
"NoRCX.drawing",
|
||||||
|
"NoRCX.m2m3",
|
||||||
|
"NoRCX.m2m4",
|
||||||
|
"NoRCX.m2m5",
|
||||||
|
"NoRCX.m2tm1",
|
||||||
|
"NoRCX.m2tm2",
|
||||||
|
"NoRCX.m3m4",
|
||||||
|
"NoRCX.m3m5",
|
||||||
|
"NoRCX.m3tm1",
|
||||||
|
"NoRCX.m3tm2",
|
||||||
|
"NoRCX.m4m5",
|
||||||
|
"NoRCX.m4tm1",
|
||||||
|
"NoRCX.m4tm2",
|
||||||
|
"NoRCX.m5tm1",
|
||||||
|
"NoRCX.m5tm2",
|
||||||
|
"NoRCX.tm1tm2",
|
||||||
|
"NoRCX.m1sub",
|
||||||
|
"NoRCX.m2sub",
|
||||||
|
"NoRCX.m3sub",
|
||||||
|
"NoRCX.m4sub",
|
||||||
|
"NoRCX.m5sub",
|
||||||
|
"NoRCX.tm1sub",
|
||||||
|
"NoRCX.tm2sub",
|
||||||
|
"BiWind.drawing",
|
||||||
|
"BiWind.OPC",
|
||||||
|
"BasPoly.drawing",
|
||||||
|
"BasPoly.label",
|
||||||
|
"BasPoly.pin",
|
||||||
|
"BasPoly.net",
|
||||||
|
"BasPoly.boundary",
|
||||||
|
"EmWind.drawing",
|
||||||
|
"EmWind.OPC",
|
||||||
|
"DeepCo.drawing",
|
||||||
|
"EmPoly.drawing",
|
||||||
|
"Varicap.drawing",
|
||||||
|
"EmWind3.drawing",
|
||||||
|
"EmWiHV3.drawing",
|
||||||
|
"ColOpen.drawing",
|
||||||
|
"ColWind.drawing",
|
||||||
|
"EmWiHV.drawing",
|
||||||
|
"DeepVia.drawing",
|
||||||
|
"LBE.drawing",
|
||||||
|
"BackMetal1.drawing",
|
||||||
|
"BackMetal1.label",
|
||||||
|
"BackMetal1.pin",
|
||||||
|
"BackMetal1.net",
|
||||||
|
"BackMetal1.boundary",
|
||||||
|
"BackMetal1.mask",
|
||||||
|
"BackMetal1.nofill",
|
||||||
|
"BackMetal1.slit",
|
||||||
|
"BackMetal1.text",
|
||||||
|
"BackMetal1.OPC",
|
||||||
|
"BackMetal1.noqrc",
|
||||||
|
"BackMetal1.res",
|
||||||
|
"BackMetal1.iprobe",
|
||||||
|
"BackMetal1.diffprb",
|
||||||
|
"BackPassiv.drawing",
|
||||||
|
"IntBondVia.drawing",
|
||||||
|
"IntBondMet.drawing",
|
||||||
|
"DevBondVia.drawing",
|
||||||
|
"DevBondMet.drawing",
|
||||||
|
"DevTrench.drawing",
|
||||||
|
"Redist.drawing",
|
||||||
|
"FBE.drawing",
|
||||||
|
"AntVia1.drawing",
|
||||||
|
"AntMetal2.drawing",
|
||||||
|
"AntMetal1.drawing",
|
||||||
|
"FLM.drawing",
|
||||||
|
"SNSRing.drawing",
|
||||||
|
"Sensor.drawing",
|
||||||
|
"SNSArms.drawing",
|
||||||
|
"SNSCMOSVia.drawing",
|
||||||
|
"SNSBotVia.drawing",
|
||||||
|
"SNSTopVia.drawing",
|
||||||
|
"NLDB.drawing",
|
||||||
|
"PLDB.drawing",
|
||||||
|
"RedBuLay.drawing",
|
||||||
|
"NLDD.drawing",
|
||||||
|
"PLDD.drawing",
|
||||||
|
"NExt.drawing",
|
||||||
|
"PExt.drawing",
|
||||||
|
"INLDPWL.drawing",
|
||||||
|
"nBuLayCut.drawing",
|
||||||
|
"GraphBot.drawing",
|
||||||
|
"GraphTop.drawing",
|
||||||
|
"GraphCont.drawing",
|
||||||
|
"SiWG.drawing",
|
||||||
|
"SiWG.nofill",
|
||||||
|
"SiGrating.drawing",
|
||||||
|
"SiNGrating.drawing",
|
||||||
|
"GraphPas.drawing",
|
||||||
|
"GraphPad.drawing",
|
||||||
|
"GraphMetal1.drawing",
|
||||||
|
"GraphMetal1.nofill",
|
||||||
|
"GraphMetal1.slit",
|
||||||
|
"GraphMetal1.OPC",
|
||||||
|
"GraphMet1L.drawing",
|
||||||
|
"GraphMet1L.nofill",
|
||||||
|
"GraphMet1L.slit",
|
||||||
|
"GraphMet1L.OPC",
|
||||||
|
"GraphGate.drawing",
|
||||||
|
"SiNWG.drawing",
|
||||||
|
"SiNWG.nofill",
|
||||||
|
"PWell.drawing",
|
||||||
|
"PWell.label",
|
||||||
|
"PWell.pin",
|
||||||
|
"PWell.net",
|
||||||
|
"PWell.boundary",
|
||||||
|
"IC.drawing",
|
||||||
|
"NoDRC.drawing",
|
||||||
|
"RadHard.drawing",
|
||||||
|
"MemCap.drawing",
|
||||||
|
"SMOS.drawing",
|
||||||
|
"Polimide.drawing",
|
||||||
|
"Polimide.label",
|
||||||
|
"Polimide.pin",
|
||||||
|
"Polimide.net",
|
||||||
|
"EXTBlock.drawing",
|
||||||
|
"NExtHV.drawing",
|
||||||
|
"PExtHV.drawing",
|
||||||
|
"MEMPAD.drawing",
|
||||||
|
"HafniumOx.drawing",
|
||||||
|
"MEMVia.drawing",
|
||||||
|
"RFMEM.drawing",
|
||||||
|
"FGEtch.drawing",
|
||||||
|
"CtrGat.drawing",
|
||||||
|
"FGImp.drawing",
|
||||||
|
"AlCuStop.drawing",
|
||||||
|
"prBoundary.drawing",
|
||||||
|
"prBoundary.label",
|
||||||
|
"prBoundary.boundary",
|
||||||
|
"Exchange0.drawing",
|
||||||
|
"Exchange0.label",
|
||||||
|
"Exchange0.pin",
|
||||||
|
"Exchange0.text",
|
||||||
|
"Exchange1.drawing",
|
||||||
|
"Exchange1.label",
|
||||||
|
"Exchange1.pin",
|
||||||
|
"Exchange1.text",
|
||||||
|
"Exchange2.drawing",
|
||||||
|
"Exchange2.label",
|
||||||
|
"Exchange2.pin",
|
||||||
|
"Exchange2.text",
|
||||||
|
"Exchange3.drawing",
|
||||||
|
"Exchange3.label",
|
||||||
|
"Exchange3.pin",
|
||||||
|
"Exchange3.text",
|
||||||
|
"Exchange4.drawing",
|
||||||
|
"Exchange4.label",
|
||||||
|
"Exchange4.pin",
|
||||||
|
"Exchange4.text",
|
||||||
|
"isoNWell.drawing",
|
||||||
|
]
|
||||||
|
|
||||||
# layers used for the analog pin check
|
# layers used for the analog pin check
|
||||||
layer_map_sky130 = {
|
layer_map_sky130 = {
|
||||||
"met4": (71, 20),
|
"met4": (71, 20),
|
||||||
@@ -70,6 +435,41 @@ def analog_pin_pos_sky130(pin_number: int, uses_3v3: bool):
|
|||||||
return 151.81 - 19.32 * pin_number - (15.64 if uses_3v3 else 0)
|
return 151.81 - 19.32 * pin_number - (15.64 if uses_3v3 else 0)
|
||||||
|
|
||||||
|
|
||||||
valid_layers = {"sky130": valid_layers_sky130}
|
valid_layers = {"sky130": valid_layers_sky130, "ihp-sg13g2": valid_layers_ihp_sg13g2}
|
||||||
layer_map = {"sky130": layer_map_sky130}
|
layer_map = {"sky130": layer_map_sky130}
|
||||||
analog_pin_pos = {"sky130": analog_pin_pos_sky130}
|
analog_pin_pos = {"sky130": analog_pin_pos_sky130}
|
||||||
|
lyp_filename = {"sky130": "sky130A.lyp", "ihp-sg13g2": "sg13g2.lyp"}
|
||||||
|
|
||||||
|
# TODO: read layer numbers from lyp file
|
||||||
|
valid_lef_port_layers = {
|
||||||
|
"sky130": {
|
||||||
|
"met1.pin": (68, 16),
|
||||||
|
"met2.pin": (69, 16),
|
||||||
|
"met3.pin": (70, 16),
|
||||||
|
"met4.pin": (71, 16),
|
||||||
|
},
|
||||||
|
"ihp-sg13g2": {
|
||||||
|
"Metal1.pin": (8, 2),
|
||||||
|
"Metal2.pin": (10, 2),
|
||||||
|
"Metal3.pin": (30, 2),
|
||||||
|
"Metal4.pin": (50, 2),
|
||||||
|
"Metal5.pin": (67, 2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
forbidden_layers = {
|
||||||
|
"sky130": ["met5.drawing", "met5.pin", "met5.label"],
|
||||||
|
"ihp-sg13g2": [
|
||||||
|
"TopMetal1.drawing",
|
||||||
|
"TopMetal1.pin",
|
||||||
|
"TopMetal1.label",
|
||||||
|
"TopMetal2.drawing",
|
||||||
|
"TopMetal2.pin",
|
||||||
|
"TopMetal2.label",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
power_pins_layer = {
|
||||||
|
"sky130": "met4",
|
||||||
|
"ihp-sg13g2": "Metal5",
|
||||||
|
}
|
||||||
|
|
||||||
|
tech_names = ["sky130", "ihp-sg13g2"]
|
||||||
|
|||||||
@@ -477,14 +477,14 @@ def test_klayout_beol_fail(gds_fail_met1_poly: str):
|
|||||||
|
|
||||||
|
|
||||||
def test_klayout_checks_pass(gds_valid: str):
|
def test_klayout_checks_pass(gds_valid: str):
|
||||||
precheck.klayout_checks(gds_valid, "TEST_valid")
|
precheck.klayout_checks(gds_valid, "TEST_valid", "sky130")
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_checks_fail_metal5(gds_fail_metal5_poly: str):
|
def test_klayout_checks_fail_metal5(gds_fail_metal5_poly: str):
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
precheck.PrecheckFailure, match=r"Forbidden layer met5\.drawing found in .+"
|
precheck.PrecheckFailure, match=r"Forbidden layer met5\.drawing found in .+"
|
||||||
):
|
):
|
||||||
precheck.klayout_checks(gds_fail_metal5_poly, "TEST_met5_error")
|
precheck.klayout_checks(gds_fail_metal5_poly, "TEST_met5_error", "sky130")
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_checks_fail_pr_boundary(gds_no_pr_boundary: str):
|
def test_klayout_checks_fail_pr_boundary(gds_no_pr_boundary: str):
|
||||||
@@ -492,7 +492,7 @@ def test_klayout_checks_fail_pr_boundary(gds_no_pr_boundary: str):
|
|||||||
precheck.PrecheckFailure,
|
precheck.PrecheckFailure,
|
||||||
match=r"prBoundary.boundary \(235/4\) layer not found in .+",
|
match=r"prBoundary.boundary \(235/4\) layer not found in .+",
|
||||||
):
|
):
|
||||||
precheck.klayout_checks(gds_no_pr_boundary, "TEST_no_prboundary")
|
precheck.klayout_checks(gds_no_pr_boundary, "TEST_no_prboundary", "sky130")
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_top_module_name(gds_invalid_macro_name: str):
|
def test_klayout_top_module_name(gds_invalid_macro_name: str):
|
||||||
@@ -500,7 +500,9 @@ def test_klayout_top_module_name(gds_invalid_macro_name: str):
|
|||||||
precheck.PrecheckFailure,
|
precheck.PrecheckFailure,
|
||||||
match="Top macro name mismatch: expected TEST_invalid_macro_name, got wrong_name",
|
match="Top macro name mismatch: expected TEST_invalid_macro_name, got wrong_name",
|
||||||
):
|
):
|
||||||
precheck.klayout_checks(gds_invalid_macro_name, "TEST_invalid_macro_name")
|
precheck.klayout_checks(
|
||||||
|
gds_invalid_macro_name, "TEST_invalid_macro_name", "sky130"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_klayout_zero_area_drc_pass(gds_valid: str):
|
def test_klayout_zero_area_drc_pass(gds_valid: str):
|
||||||
@@ -514,7 +516,7 @@ def test_klayout_zero_area_drc_fail(gds_zero_area: str):
|
|||||||
|
|
||||||
def test_shapes_outside_area(gds_shapes_outside_area: str):
|
def test_shapes_outside_area(gds_shapes_outside_area: str):
|
||||||
with pytest.raises(precheck.PrecheckFailure, match="Shapes outside project area"):
|
with pytest.raises(precheck.PrecheckFailure, match="Shapes outside project area"):
|
||||||
precheck.boundary_check(gds_shapes_outside_area)
|
precheck.boundary_check(gds_shapes_outside_area, "sky130")
|
||||||
|
|
||||||
|
|
||||||
def test_wrong_power_pins_1(verilog_lef_wrong_power_pins: tuple[str, str]):
|
def test_wrong_power_pins_1(verilog_lef_wrong_power_pins: tuple[str, str]):
|
||||||
@@ -558,6 +560,7 @@ def test_pin_analog_example(gds_lef_analog_example: tuple[str, str]):
|
|||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_example",
|
"TEST_analog_example",
|
||||||
False,
|
False,
|
||||||
|
"sky130",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -569,6 +572,7 @@ def test_pin_analog_power_compat(gds_lef_analog_power_compat: tuple[str, str]):
|
|||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_power_compat",
|
"TEST_analog_power_compat",
|
||||||
False,
|
False,
|
||||||
|
"sky130",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -584,6 +588,7 @@ def test_pin_analog_wrong_vgnd(gds_lef_analog_wrong_vgnd: tuple[str, str]):
|
|||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_wrong_vgnd",
|
"TEST_analog_wrong_vgnd",
|
||||||
False,
|
False,
|
||||||
|
"sky130",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -599,6 +604,7 @@ def test_pin_analog_overlapping_vgnd(gds_lef_analog_overlapping_vgnd: tuple[str,
|
|||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_overlapping_vgnd",
|
"TEST_analog_overlapping_vgnd",
|
||||||
False,
|
False,
|
||||||
|
"sky130",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -610,6 +616,7 @@ def test_pin_analog_compound_vgnd(gds_lef_analog_compound_vgnd: tuple[str, str])
|
|||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_compound_vgnd",
|
"TEST_analog_compound_vgnd",
|
||||||
False,
|
False,
|
||||||
|
"sky130",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -621,6 +628,7 @@ def test_pin_analog_example_3v3(gds_lef_analog_example_3v3: tuple[str, str]):
|
|||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_example_3v3",
|
"TEST_analog_example_3v3",
|
||||||
True,
|
True,
|
||||||
|
"sky130",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -636,6 +644,7 @@ def test_pin_analog_3v3_mismatch1(gds_lef_analog_example: tuple[str, str]):
|
|||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_example",
|
"TEST_analog_example",
|
||||||
True,
|
True,
|
||||||
|
"sky130",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -651,6 +660,7 @@ def test_pin_analog_3v3_mismatch2(gds_lef_analog_example_3v3: tuple[str, str]):
|
|||||||
"../def/analog/tt_analog_1x2.def",
|
"../def/analog/tt_analog_1x2.def",
|
||||||
"TEST_analog_example_3v3",
|
"TEST_analog_example_3v3",
|
||||||
False,
|
False,
|
||||||
|
"sky130",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user