diff --git a/precheck/pin_check.py b/precheck/pin_check.py index 1618ff3..8a3f699 100644 --- a/precheck/pin_check.py +++ b/precheck/pin_check.py @@ -1,95 +1,11 @@ import logging import re -from collections.abc import Iterable from itertools import combinations -from numbers import Real import gdstk 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): # parse fixed-point numbers with 3 digits after the decimal point # e.g. '20.470' to 20470 @@ -245,20 +161,6 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str): lef_errors += 1 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: if current_pin not in lef_ports: logging.error(f"Pin {current_pin} not found in {lef}") @@ -365,34 +267,26 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str): "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 - for current_pin, lef_rects in sorted(lef_ports_orig.items()): + for current_pin, lef_rects in sorted(lef_ports.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_layer = gds_layers[layer + ".pin"] pin_ok = False - for poly in merged_layers[layer + ".pin"]: + for poly in top.polygons: + poly_layer = (poly.layer, poly.datatype) 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 poly_layer == pin_layer: + pin_ok = True if not pin_ok: logging.error( diff --git a/precheck/test_precheck.py b/precheck/test_precheck.py index 74f8e61..70eaf86 100644 --- a/precheck/test_precheck.py +++ b/precheck/test_precheck.py @@ -113,9 +113,9 @@ def generate_analog_example( lef_file: str, toplevel: str, vpwr_layer: str, - vpwr_boxes: str, + vpwr_box: str, vgnd_layer: str, - vgnd_boxes: str, + vgnd_box: str, ): with open(tcl_file, "w") as f: f.write( @@ -125,30 +125,26 @@ def read ../def/analog/tt_block_1x2_pg_ana.def cellname rename tt_um_template {toplevel} # VPWR - foreach vpwr_box {{ {vpwr_boxes} }} {{ - box {{*}}$vpwr_box - paint {vpwr_layer} - label VPWR FreeSans {vpwr_layer} - }} - port VPWR makeall n + box {vpwr_box} + paint {vpwr_layer} + label VPWR FreeSans {vpwr_layer} + port VPWR make n port VPWR use power port VPWR class bidirectional 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 + box {vgnd_box} + paint {vgnd_layer} + label VGND FreeSans {vgnd_layer} + port VGND make n port VGND use ground port VGND class bidirectional port conn n s e w # Export gds write {gds_file} - lef write {lef_file} + lef write {lef_file} -pinonly """ ) ) @@ -180,9 +176,9 @@ def gds_lef_analog_example(tmp_path_factory: pytest.TempPathFactory): str(lef_file), "TEST_analog_example", "met4", - "{100 500 250 22076}", + "100 500 250 22076", "met4", - "{4900 500 5050 22076}", + "4900 500 5050 22076", ) return str(gds_file), str(lef_file) @@ -200,9 +196,9 @@ def gds_lef_analog_wrong_vgnd(tmp_path_factory: pytest.TempPathFactory): str(lef_file), "TEST_analog_wrong_vgnd", "met4", - "{100 500 250 22076}", + "100 500 250 22076", "met3", - "{4900 500 5250 12076}", + "4900 500 5250 12076", ) return str(gds_file), str(lef_file) @@ -220,29 +216,9 @@ def gds_lef_analog_overlapping_vgnd(tmp_path_factory: pytest.TempPathFactory): str(lef_file), "TEST_analog_overlapping_vgnd", "met4", - "{100 500 250 22076}", + "100 500 250 22076", "met4", - "{349 20 549 22504}", - ) - return str(gds_file), str(lef_file) - - -@pytest.fixture(scope="session") -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.""" - tcl_file = tmp_path_factory.mktemp("tcl") / "TEST_analog_example.tcl" - gds_file = tmp_path_factory.mktemp("gds") / "TEST_analog_example.gds" - lef_file = tmp_path_factory.mktemp("lef") / "TEST_analog_example.lef" - - generate_analog_example( - str(tcl_file), - str(gds_file), - str(lef_file), - "TEST_analog_compound_vgnd", - "met4", - "{100 500 250 22076}", - "met4", - "{4900 500 5050 12076} {4900 12000 5050 22076}", + "349 20 549 22504", ) return str(gds_file), str(lef_file) @@ -346,13 +322,3 @@ def test_pin_analog_overlapping_vgnd(gds_lef_analog_overlapping_vgnd: tuple[str, "../def/analog/tt_block_1x2_pg_ana.def", "TEST_analog_overlapping_vgnd", ) - - -def test_pin_analog_compound_vgnd(gds_lef_analog_compound_vgnd: tuple[str, str]): - gds_file, lef_file = gds_lef_analog_compound_vgnd - precheck.pin_check( - gds_file, - lef_file, - "../def/analog/tt_block_1x2_pg_ana.def", - "TEST_analog_compound_vgnd", - )