mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
feat(precheck): combine rectangles within the same port
This commit is contained in:
+112
-6
@@ -1,11 +1,95 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from collections.abc import Iterable
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
|
from numbers import Real
|
||||||
|
|
||||||
import gdstk
|
import gdstk
|
||||||
from precheck_failure import PrecheckFailure
|
from precheck_failure import PrecheckFailure
|
||||||
|
|
||||||
|
|
||||||
|
def canonicalize_rectangles(
|
||||||
|
rects: Iterable[Iterable[Real]],
|
||||||
|
) -> Iterable[Iterable[Real]]:
|
||||||
|
# lists all maximal rectangles covered by the union of input rectangles
|
||||||
|
|
||||||
|
sweep_events = {}
|
||||||
|
for lx, by, rx, ty in rects:
|
||||||
|
sweep_events[by] = sweep_events.get(by, {})
|
||||||
|
sweep_events[by][lx] = sweep_events[by].get(lx, 0) + 1
|
||||||
|
sweep_events[by][rx] = sweep_events[by].get(rx, 0) - 1
|
||||||
|
sweep_events[ty] = sweep_events.get(ty, {})
|
||||||
|
sweep_events[ty][lx] = sweep_events[ty].get(lx, 0) - 1
|
||||||
|
sweep_events[ty][rx] = sweep_events[ty].get(rx, 0) + 1
|
||||||
|
|
||||||
|
closed_rects = []
|
||||||
|
open_rects = {}
|
||||||
|
cross_section_events = {}
|
||||||
|
for y, sweep_line_events in sorted(sweep_events.items()):
|
||||||
|
old_multiplicity = 0
|
||||||
|
new_multiplicity = 0
|
||||||
|
is_covered, covered_intervals, covered_start = False, [], None
|
||||||
|
is_removed, removed_intervals, removed_start = False, [], None
|
||||||
|
for x in sorted(set(cross_section_events).union(sweep_line_events)):
|
||||||
|
old_delta = cross_section_events.get(x, 0)
|
||||||
|
new_delta = sweep_line_events.get(x, 0) + old_delta
|
||||||
|
old_multiplicity += old_delta
|
||||||
|
new_multiplicity += new_delta
|
||||||
|
assert old_multiplicity >= 0
|
||||||
|
assert new_multiplicity >= 0
|
||||||
|
was_covered, was_removed = is_covered, is_removed
|
||||||
|
is_covered = new_multiplicity > 0
|
||||||
|
is_removed = old_multiplicity > 0 and new_multiplicity == 0
|
||||||
|
if was_covered and not is_covered:
|
||||||
|
assert covered_start is not None
|
||||||
|
covered_intervals.append((covered_start, x))
|
||||||
|
covered_start = None
|
||||||
|
if was_removed and not is_removed:
|
||||||
|
assert removed_start is not None
|
||||||
|
removed_intervals.append((removed_start, x))
|
||||||
|
removed_start = None
|
||||||
|
if is_covered and not was_covered:
|
||||||
|
assert covered_start is None
|
||||||
|
covered_start = x
|
||||||
|
if is_removed and not was_removed:
|
||||||
|
assert removed_start is None
|
||||||
|
removed_start = x
|
||||||
|
|
||||||
|
for x, m in sorted(sweep_line_events.items()):
|
||||||
|
cross_section_events[x] = cross_section_events.get(x, 0) + m
|
||||||
|
if cross_section_events[x] == 0:
|
||||||
|
del cross_section_events[x]
|
||||||
|
|
||||||
|
for (lx, rx), by in open_rects.items():
|
||||||
|
closed = False
|
||||||
|
for ix, jx in removed_intervals:
|
||||||
|
if ix < rx and lx < jx:
|
||||||
|
closed = True
|
||||||
|
if closed:
|
||||||
|
closed_rects.append((lx, by, rx, y))
|
||||||
|
|
||||||
|
kept_intervals = [
|
||||||
|
(b, c)
|
||||||
|
for ((a, b), (c, d)) in zip(
|
||||||
|
[(None, None)] + removed_intervals, removed_intervals + [(None, None)]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
open_rects_new = {}
|
||||||
|
for ix, jx in covered_intervals:
|
||||||
|
open_rects_new[(ix, jx)] = y
|
||||||
|
for (lx, rx), by in open_rects.items():
|
||||||
|
for ix, jx in kept_intervals:
|
||||||
|
if (ix is None or ix < rx) and (jx is None or lx < jx):
|
||||||
|
clx = lx if ix is None else max(lx, ix)
|
||||||
|
crx = rx if jx is None else min(rx, jx)
|
||||||
|
open_rects_new[(clx, crx)] = min(
|
||||||
|
open_rects_new.get((clx, crx), by), by
|
||||||
|
)
|
||||||
|
open_rects = open_rects_new
|
||||||
|
|
||||||
|
return sorted(set(closed_rects))
|
||||||
|
|
||||||
|
|
||||||
def parsefp3(value: str):
|
def parsefp3(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
|
||||||
@@ -161,6 +245,20 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
|
|||||||
lef_errors += 1
|
lef_errors += 1
|
||||||
current_pin = None
|
current_pin = None
|
||||||
|
|
||||||
|
lef_ports_orig = lef_ports
|
||||||
|
lef_ports = {}
|
||||||
|
for current_pin, ports_orig in lef_ports_orig.items():
|
||||||
|
ports_by_layers = {}
|
||||||
|
for layer, lx, by, rx, ty in ports_orig:
|
||||||
|
if layer not in ports_by_layers:
|
||||||
|
ports_by_layers[layer] = []
|
||||||
|
ports_by_layers[layer].append((lx, by, rx, ty))
|
||||||
|
ports = []
|
||||||
|
for layer, rects in sorted(ports_by_layers.items()):
|
||||||
|
for lx, by, rx, ty in canonicalize_rectangles(rects):
|
||||||
|
ports.append((layer, lx, by, rx, ty))
|
||||||
|
lef_ports[current_pin] = ports
|
||||||
|
|
||||||
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}")
|
||||||
@@ -267,26 +365,34 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
|
|||||||
"met4.pin": (71, 16),
|
"met4.pin": (71, 16),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.items()):
|
for current_pin, lef_rects in sorted(lef_ports_orig.items()):
|
||||||
for layer, lx, by, rx, ty in lef_rects:
|
for layer, lx, by, rx, ty in lef_rects:
|
||||||
if layer + ".pin" not in gds_layers:
|
if layer + ".pin" not in gds_layers:
|
||||||
raise PrecheckFailure(
|
raise PrecheckFailure(
|
||||||
f"Unexpected port layer in LEF: {current_pin} is on layer {layer}"
|
f"Unexpected port layer in LEF: {current_pin} is on layer {layer}"
|
||||||
)
|
)
|
||||||
pin_layer = gds_layers[layer + ".pin"]
|
|
||||||
|
|
||||||
pin_ok = False
|
pin_ok = False
|
||||||
for poly in top.polygons:
|
for poly in merged_layers[layer + ".pin"]:
|
||||||
poly_layer = (poly.layer, poly.datatype)
|
|
||||||
if poly.contain_all(
|
if poly.contain_all(
|
||||||
((lx + 1) / 1000, (by + 1) / 1000),
|
((lx + 1) / 1000, (by + 1) / 1000),
|
||||||
((rx - 1) / 1000, (by + 1) / 1000),
|
((rx - 1) / 1000, (by + 1) / 1000),
|
||||||
((lx + 1) / 1000, (ty - 1) / 1000),
|
((lx + 1) / 1000, (ty - 1) / 1000),
|
||||||
((rx - 1) / 1000, (ty - 1) / 1000),
|
((rx - 1) / 1000, (ty - 1) / 1000),
|
||||||
):
|
):
|
||||||
if poly_layer == pin_layer:
|
pin_ok = True
|
||||||
pin_ok = True
|
|
||||||
|
|
||||||
if not pin_ok:
|
if not pin_ok:
|
||||||
logging.error(
|
logging.error(
|
||||||
|
|||||||
Reference in New Issue
Block a user