2022-03-27 23:43:52 -07:00
|
|
|
from typing import Tuple, List, Dict, Set, Optional, Union, Sequence, Mapping
|
|
|
|
from collections import defaultdict
|
|
|
|
from pprint import pformat
|
|
|
|
import logging
|
|
|
|
|
|
|
|
import numpy
|
|
|
|
from numpy.typing import NDArray, ArrayLike
|
|
|
|
from pyclipper import scale_to_clipper, scale_from_clipper, PyPolyNode
|
|
|
|
|
2022-03-29 21:23:27 -07:00
|
|
|
from .types import connectivity_t, layer_t, contour_t
|
2022-03-27 23:43:52 -07:00
|
|
|
from .poly import poly_contains_points
|
|
|
|
from .clipper import union_nonzero, union_evenodd, intersection_evenodd, hier2oriented
|
2022-03-29 21:23:27 -07:00
|
|
|
from .tracker import NetsInfo, NetName
|
2022-03-27 23:43:52 -07:00
|
|
|
from .utils import connectivity2layers
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2022-03-29 21:28:35 -07:00
|
|
|
def trace_connectivity(
|
2022-03-27 23:43:52 -07:00
|
|
|
polys: Mapping[layer_t, Sequence[ArrayLike]],
|
|
|
|
labels: Mapping[layer_t, Sequence[Tuple[float, float, str]]],
|
|
|
|
connectivity: Sequence[Tuple[layer_t, Optional[layer_t], layer_t]],
|
|
|
|
label_mapping: Optional[Mapping[layer_t, layer_t]] = None,
|
|
|
|
clipper_scale_factor: int = int(2 ** 24),
|
|
|
|
) -> NetsInfo:
|
|
|
|
|
|
|
|
metal_layers, via_layers = connectivity2layers(connectivity)
|
|
|
|
if label_mapping is None:
|
|
|
|
label_mapping = {layer: layer for layer in metal_layers}
|
|
|
|
|
|
|
|
metal_polys = {layer: union_input_polys(scale_to_clipper(polys[layer], clipper_scale_factor))
|
|
|
|
for layer in metal_layers}
|
|
|
|
via_polys = {layer: union_input_polys(scale_to_clipper(polys[layer], clipper_scale_factor))
|
|
|
|
for layer in via_layers}
|
|
|
|
|
|
|
|
nets_info = NetsInfo()
|
|
|
|
|
2022-03-29 21:23:27 -07:00
|
|
|
merge_groups: List[List[NetName]] = []
|
2022-03-30 23:57:06 -07:00
|
|
|
for layer in metal_layers:
|
2022-03-27 23:43:52 -07:00
|
|
|
point_xys = []
|
|
|
|
point_names = []
|
2022-03-30 23:57:06 -07:00
|
|
|
for x, y, point_name in labels[layer]:
|
2022-03-27 23:43:52 -07:00
|
|
|
point_xys.append((x, y))
|
|
|
|
point_names.append(point_name)
|
|
|
|
|
|
|
|
for poly in metal_polys[layer]:
|
|
|
|
found_nets = label_poly(poly, point_xys, point_names, clipper_scale_factor)
|
|
|
|
|
2022-03-29 21:23:27 -07:00
|
|
|
name: Optional[str]
|
2022-03-27 23:43:52 -07:00
|
|
|
if found_nets:
|
2022-03-29 21:23:27 -07:00
|
|
|
name = NetName(found_nets[0])
|
2022-03-27 23:43:52 -07:00
|
|
|
else:
|
2022-03-29 21:23:27 -07:00
|
|
|
name = NetName() # Anonymous net
|
2022-03-27 23:43:52 -07:00
|
|
|
|
2022-03-30 23:58:53 -07:00
|
|
|
nets_info.nets[name][layer].append(poly)
|
2022-03-27 23:43:52 -07:00
|
|
|
|
|
|
|
if len(found_nets) > 1:
|
|
|
|
# Found a short
|
|
|
|
logger.warning(f'Nets {found_nets} are shorted on layer {layer} in poly:\n {pformat(poly)}')
|
2022-03-29 21:23:27 -07:00
|
|
|
merge_groups.append([name] + [NetName(nn) for nn in found_nets[1:]])
|
2022-03-27 23:43:52 -07:00
|
|
|
|
|
|
|
for group in merge_groups:
|
|
|
|
first_net, *defunct_nets = group
|
|
|
|
for defunct_net in defunct_nets:
|
|
|
|
nets_info.merge(first_net, defunct_net)
|
|
|
|
|
|
|
|
#
|
|
|
|
# Take EVENODD union within each net
|
|
|
|
# & stay in EVENODD-friendly representation
|
|
|
|
#
|
|
|
|
for net in nets_info.nets.values():
|
|
|
|
for layer in net:
|
|
|
|
#net[layer] = union_evenodd(hier2oriented(net[layer]))
|
|
|
|
net[layer] = hier2oriented(net[layer])
|
|
|
|
|
|
|
|
for layer in via_polys:
|
|
|
|
via_polys[layer] = hier2oriented(via_polys[layer])
|
|
|
|
|
|
|
|
|
|
|
|
merge_pairs = find_merge_pairs(connectivity, nets_info.nets, via_polys)
|
|
|
|
for net_a, net_b in merge_pairs:
|
|
|
|
nets_info.merge(net_a, net_b)
|
|
|
|
|
|
|
|
return nets_info
|
|
|
|
|
|
|
|
|
|
|
|
def union_input_polys(polys: List[ArrayLike]) -> List[PyPolyNode]:
|
|
|
|
for poly in polys:
|
|
|
|
if (numpy.abs(poly) % 1).any():
|
|
|
|
logger.warning('Warning: union_polys got non-integer coordinates; all values will be truncated.')
|
|
|
|
break
|
|
|
|
|
|
|
|
poly_tree = union_nonzero(polys)
|
|
|
|
if poly_tree is None:
|
|
|
|
return []
|
|
|
|
|
|
|
|
# Partially flatten the tree, reclassifying all the "outer" (non-hole) nodes as new root nodes
|
|
|
|
unvisited_nodes = [poly_tree]
|
|
|
|
outer_nodes = []
|
|
|
|
while unvisited_nodes:
|
|
|
|
node = unvisited_nodes.pop() # node will be the tree parent node (a container), or a hole
|
|
|
|
for poly in node.Childs:
|
|
|
|
outer_nodes.append(poly)
|
|
|
|
for hole in poly.Childs: # type: ignore
|
|
|
|
unvisited_nodes.append(hole)
|
|
|
|
|
|
|
|
return outer_nodes
|
|
|
|
|
|
|
|
|
|
|
|
def label_poly(
|
|
|
|
poly: PyPolyNode,
|
|
|
|
point_xys: ArrayLike,
|
|
|
|
point_names: Sequence[str],
|
|
|
|
clipper_scale_factor: int = int(2 ** 24),
|
|
|
|
) -> List[str]:
|
|
|
|
|
|
|
|
poly_contour = scale_from_clipper(poly.Contour, clipper_scale_factor)
|
|
|
|
inside = poly_contains_points(poly_contour, point_xys)
|
|
|
|
for hole in poly.Childs:
|
|
|
|
hole_contour = scale_from_clipper(hole.Contour, clipper_scale_factor)
|
|
|
|
inside &= ~poly_contains_points(hole_contour, point_xys)
|
|
|
|
|
|
|
|
inside_nets = sorted([net_name for net_name, ii in zip(point_names, inside) if ii])
|
|
|
|
|
|
|
|
if inside.any():
|
|
|
|
return inside_nets
|
|
|
|
else:
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
def find_merge_pairs(
|
|
|
|
connectivity: connectivity_t,
|
2022-03-29 21:23:27 -07:00
|
|
|
nets: Mapping[NetName, Mapping[layer_t, Sequence[contour_t]]],
|
2022-03-27 23:43:52 -07:00
|
|
|
via_polys: Mapping[layer_t, Sequence[contour_t]],
|
2022-03-29 21:23:27 -07:00
|
|
|
) -> Set[Tuple[NetName, NetName]]:
|
2022-03-27 23:43:52 -07:00
|
|
|
#
|
|
|
|
# Merge nets based on via connectivity
|
|
|
|
#
|
|
|
|
merge_pairs = set()
|
|
|
|
for top_layer, via_layer, bot_layer in connectivity:
|
|
|
|
if via_layer is not None:
|
|
|
|
vias = via_polys[via_layer]
|
|
|
|
if not vias:
|
|
|
|
continue
|
|
|
|
|
|
|
|
for top_name in nets.keys():
|
|
|
|
top_polys = nets[top_name][top_layer]
|
|
|
|
if not top_polys:
|
|
|
|
continue
|
|
|
|
|
|
|
|
for bot_name in nets.keys():
|
|
|
|
if bot_name == top_name:
|
|
|
|
continue
|
2022-03-29 21:23:27 -07:00
|
|
|
name_pair = tuple(sorted((top_name, bot_name)))
|
2022-03-27 23:43:52 -07:00
|
|
|
if name_pair in merge_pairs:
|
|
|
|
continue
|
|
|
|
|
|
|
|
bot_polys = nets[bot_name][bot_layer]
|
|
|
|
if not bot_polys:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if via_layer is not None:
|
|
|
|
via_top = intersection_evenodd(top_polys, vias)
|
|
|
|
overlap = intersection_evenodd(via_top, bot_polys)
|
|
|
|
else:
|
|
|
|
overlap = intersection_evenodd(top_polys, bot_polys) # TODO verify there aren't any suspicious corner cases for this
|
|
|
|
|
|
|
|
if not overlap:
|
|
|
|
continue
|
|
|
|
|
|
|
|
merge_pairs.add(name_pair)
|
|
|
|
return merge_pairs
|