mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 12:46:08 +01:00
feat(precheck): add pin_check for gf180mcuD
and also enable magic drc
This commit is contained in:
+53
-44
@@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
from functools import partial
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
from numbers import Real
|
from numbers import Real
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ def canonicalize_rectangles(
|
|||||||
return sorted(set(closed_rects))
|
return sorted(set(closed_rects))
|
||||||
|
|
||||||
|
|
||||||
def parsefp3(value: str):
|
def parse_fp3(value: str):
|
||||||
# parse fixed-point numbers with 3 digits after the decimal point
|
# parse fixed-point numbers with 3 digits after the decimal point
|
||||||
# e.g. '20.470' to 20470
|
# e.g. '20.470' to 20470
|
||||||
ip, fp = value.split(".")
|
ip, fp = value.split(".")
|
||||||
@@ -99,6 +100,14 @@ def parsefp3(value: str):
|
|||||||
return int(mul)
|
return int(mul)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_dbu_to_nm(value: str, dbu_per_micron: int):
|
||||||
|
# parse a value in database units into nanometers
|
||||||
|
assert dbu_per_micron != 0
|
||||||
|
val_dbu = int(value)
|
||||||
|
assert (val_dbu * 1000) % dbu_per_micron == 0
|
||||||
|
return (val_dbu * 1000) // dbu_per_micron
|
||||||
|
|
||||||
|
|
||||||
def pin_check(
|
def pin_check(
|
||||||
gds: str, lef: str, template_def: str, toplevel: str, uses_3v3: bool, tech: str
|
gds: str, lef: str, template_def: str, toplevel: str, uses_3v3: bool, tech: str
|
||||||
):
|
):
|
||||||
@@ -129,18 +138,16 @@ def pin_check(
|
|||||||
if line.startswith("UNITS "):
|
if line.startswith("UNITS "):
|
||||||
match = units_re.match(line)
|
match = units_re.match(line)
|
||||||
(dbu_per_micron,) = map(int, match.groups())
|
(dbu_per_micron,) = map(int, match.groups())
|
||||||
|
parse_nm = partial(parse_dbu_to_nm, dbu_per_micron=dbu_per_micron)
|
||||||
elif line.startswith("DIEAREA "):
|
elif line.startswith("DIEAREA "):
|
||||||
match = diearea_re.match(line)
|
match = diearea_re.match(line)
|
||||||
lx, by, rx, ty = map(int, match.groups())
|
lx, by, rx, ty = map(parse_nm, match.groups())
|
||||||
if (lx, by) != (0, 0):
|
if (lx, by) != (0, 0):
|
||||||
raise PrecheckFailure(
|
raise PrecheckFailure(
|
||||||
"Wrong die origin in template DEF, expecting (0, 0)"
|
"Wrong die origin in template DEF, expecting (0, 0)"
|
||||||
)
|
)
|
||||||
assert dbu_per_micron != 0
|
die_width = rx
|
||||||
assert (rx * 1000) % dbu_per_micron == 0
|
die_height = ty
|
||||||
assert (ty * 1000) % dbu_per_micron == 0
|
|
||||||
die_width = (rx * 1000) // dbu_per_micron
|
|
||||||
die_height = (ty * 1000) // dbu_per_micron
|
|
||||||
elif line.startswith("PINS "):
|
elif line.startswith("PINS "):
|
||||||
match = pins_re.match(line)
|
match = pins_re.match(line)
|
||||||
pin_count = int(match.group(1))
|
pin_count = int(match.group(1))
|
||||||
@@ -164,12 +171,12 @@ def pin_check(
|
|||||||
line = next(f)
|
line = next(f)
|
||||||
match = layer_re.match(line)
|
match = layer_re.match(line)
|
||||||
layer, lx, by, rx, ty = match.groups()
|
layer, lx, by, rx, ty = match.groups()
|
||||||
lx, by, rx, ty = map(int, (lx, by, rx, ty))
|
lx, by, rx, ty = map(parse_nm, (lx, by, rx, ty))
|
||||||
|
|
||||||
line = next(f)
|
line = next(f)
|
||||||
match = placed_re.match(line)
|
match = placed_re.match(line)
|
||||||
ox, oy, direction = match.groups()
|
ox, oy, direction = match.groups()
|
||||||
ox, oy = map(int, (ox, oy))
|
ox, oy = map(parse_nm, (ox, oy))
|
||||||
|
|
||||||
if pin_name in def_pins:
|
if pin_name in def_pins:
|
||||||
raise PrecheckFailure("Duplicate pin in template DEF")
|
raise PrecheckFailure("Duplicate pin in template DEF")
|
||||||
@@ -214,14 +221,14 @@ def pin_check(
|
|||||||
elif macro_active:
|
elif macro_active:
|
||||||
if line.startswith("ORIGIN "):
|
if line.startswith("ORIGIN "):
|
||||||
match = origin_re.match(line)
|
match = origin_re.match(line)
|
||||||
lx, by = map(parsefp3, match.groups())
|
lx, by = map(parse_fp3, match.groups())
|
||||||
if lx != 0 or by != 0:
|
if lx != 0 or by != 0:
|
||||||
raise PrecheckFailure(
|
raise PrecheckFailure(
|
||||||
"Wrong die origin in LEF, expecting (0, 0)"
|
"Wrong die origin in LEF, expecting (0, 0)"
|
||||||
)
|
)
|
||||||
elif line.startswith("SIZE "):
|
elif line.startswith("SIZE "):
|
||||||
match = size_re.match(line)
|
match = size_re.match(line)
|
||||||
rx, ty = map(parsefp3, match.groups())
|
rx, ty = map(parse_fp3, match.groups())
|
||||||
if (rx, ty) != (die_width, die_height):
|
if (rx, ty) != (die_width, die_height):
|
||||||
raise PrecheckFailure(
|
raise PrecheckFailure(
|
||||||
f"Inconsistent die area between LEF and template DEF: ({rx}, {ty}) != ({die_width}, {die_height})"
|
f"Inconsistent die area between LEF and template DEF: ({rx}, {ty}) != ({die_width}, {die_height})"
|
||||||
@@ -250,7 +257,7 @@ def pin_check(
|
|||||||
line = next(f).strip()
|
line = next(f).strip()
|
||||||
while line.startswith("RECT "):
|
while line.startswith("RECT "):
|
||||||
match = rect_re.match(line)
|
match = rect_re.match(line)
|
||||||
lx, by, rx, ty = map(parsefp3, match.groups())
|
lx, by, rx, ty = map(parse_fp3, match.groups())
|
||||||
pin_rects += 1
|
pin_rects += 1
|
||||||
if current_pin not in lef_ports:
|
if current_pin not in lef_ports:
|
||||||
lef_ports[current_pin] = []
|
lef_ports[current_pin] = []
|
||||||
@@ -369,41 +376,43 @@ def pin_check(
|
|||||||
raise PrecheckFailure("Wrong cell at GDS top-level")
|
raise PrecheckFailure("Wrong cell at GDS top-level")
|
||||||
top = top[0]
|
top = top[0]
|
||||||
|
|
||||||
gds_layers = valid_lef_port_layers[tech]
|
|
||||||
gds_layer_lookup = {j: i for i, j in gds_layers.items()}
|
|
||||||
polygon_list = {layer: [] for layer in gds_layers}
|
|
||||||
for poly in top.polygons:
|
|
||||||
poly_layer = (poly.layer, poly.datatype)
|
|
||||||
layer_name = gds_layer_lookup.get(poly_layer, None)
|
|
||||||
if layer_name is not None:
|
|
||||||
polygon_list[layer_name].append(poly)
|
|
||||||
merged_layers = {}
|
|
||||||
for layer in gds_layers:
|
|
||||||
merged_layers[layer] = gdstk.boolean(polygon_list[layer], [], "or")
|
|
||||||
|
|
||||||
gds_errors = 0
|
gds_errors = 0
|
||||||
for current_pin, lef_rects in sorted(lef_ports_orig.items()):
|
|
||||||
for layer, lx, by, rx, ty in lef_rects:
|
|
||||||
if layer + ".pin" not in gds_layers:
|
|
||||||
raise PrecheckFailure(
|
|
||||||
f"Unexpected port layer in LEF: {current_pin} is on layer {layer}"
|
|
||||||
)
|
|
||||||
|
|
||||||
pin_ok = False
|
if tech != "gf180mcuD":
|
||||||
for poly in merged_layers[layer + ".pin"]:
|
gds_layers = valid_lef_port_layers[tech]
|
||||||
if poly.contain_all(
|
gds_layer_lookup = {j: i for i, j in gds_layers.items()}
|
||||||
((lx + 1) / 1000, (by + 1) / 1000),
|
polygon_list = {layer: [] for layer in gds_layers}
|
||||||
((rx - 1) / 1000, (by + 1) / 1000),
|
for poly in top.polygons:
|
||||||
((lx + 1) / 1000, (ty - 1) / 1000),
|
poly_layer = (poly.layer, poly.datatype)
|
||||||
((rx - 1) / 1000, (ty - 1) / 1000),
|
layer_name = gds_layer_lookup.get(poly_layer, None)
|
||||||
):
|
if layer_name is not None:
|
||||||
pin_ok = True
|
polygon_list[layer_name].append(poly)
|
||||||
|
merged_layers = {}
|
||||||
|
for layer in gds_layers:
|
||||||
|
merged_layers[layer] = gdstk.boolean(polygon_list[layer], [], "or")
|
||||||
|
|
||||||
if not pin_ok:
|
for current_pin, lef_rects in sorted(lef_ports_orig.items()):
|
||||||
logging.error(
|
for layer, lx, by, rx, ty in lef_rects:
|
||||||
f"Port {current_pin} missing from layer {layer}.pin in {gds}"
|
if layer + ".pin" not in gds_layers:
|
||||||
)
|
raise PrecheckFailure(
|
||||||
gds_errors += 1
|
f"Unexpected port layer in LEF: {current_pin} is on layer {layer}"
|
||||||
|
)
|
||||||
|
|
||||||
|
pin_ok = False
|
||||||
|
for poly in merged_layers[layer + ".pin"]:
|
||||||
|
if poly.contain_all(
|
||||||
|
((lx + 1) / 1000, (by + 1) / 1000),
|
||||||
|
((rx - 1) / 1000, (by + 1) / 1000),
|
||||||
|
((lx + 1) / 1000, (ty - 1) / 1000),
|
||||||
|
((rx - 1) / 1000, (ty - 1) / 1000),
|
||||||
|
):
|
||||||
|
pin_ok = True
|
||||||
|
|
||||||
|
if not pin_ok:
|
||||||
|
logging.error(
|
||||||
|
f"Port {current_pin} missing from layer {layer}.pin in {gds}"
|
||||||
|
)
|
||||||
|
gds_errors += 1
|
||||||
|
|
||||||
if lef_errors > 0 or gds_errors > 0:
|
if lef_errors > 0 or gds_errors > 0:
|
||||||
err_list = []
|
err_list = []
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ def main():
|
|||||||
{
|
{
|
||||||
"name": "Magic DRC",
|
"name": "Magic DRC",
|
||||||
"check": lambda: magic_drc(gds_file, top_module),
|
"check": lambda: magic_drc(gds_file, top_module),
|
||||||
"techs": ["sky130A"],
|
"techs": ["sky130A", "gf180mcuD"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "KLayout FEOL",
|
"name": "KLayout FEOL",
|
||||||
@@ -377,7 +377,6 @@ def main():
|
|||||||
"check": lambda: pin_check(
|
"check": lambda: pin_check(
|
||||||
gds_file, lef_file, template_def, top_module, uses_3v3, tech
|
gds_file, lef_file, template_def, top_module, uses_3v3, tech
|
||||||
),
|
),
|
||||||
"techs": ["sky130A", "ihp-sg13g2"],
|
|
||||||
},
|
},
|
||||||
{"name": "Boundary check", "check": lambda: boundary_check(gds_file, tech)},
|
{"name": "Boundary check", "check": lambda: boundary_check(gds_file, tech)},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user