feat(precheck): initial IHP support

still missing the IHP Klayout DRC deck
This commit is contained in:
Uri Shaked
2025-04-16 08:28:51 +03:00
parent 49b57ea0bb
commit c350c2ed16
5 changed files with 532 additions and 58 deletions
+2 -2
View File
@@ -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 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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"]
+15 -5
View File
@@ -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",
) )