diff --git a/connectivity.oas b/connectivity.oas new file mode 100644 index 0000000..865bf31 Binary files /dev/null and b/connectivity.oas differ diff --git a/example.py b/example.py index 3aadff2..8a6be52 100644 --- a/example.py +++ b/example.py @@ -1,3 +1,5 @@ +from pprint import pformat + from masque.file import gdsii, oasis import snarl @@ -18,3 +20,12 @@ topcell = cells['top'] polys, labels = snarl.interfaces.masque.read_topcell(topcell, connectivity) nets_info = snarl.check_connectivity(polys, labels, connectivity) +print('\nFinal nets:') +print([kk for kk in nets_info.nets if isinstance(kk.name, str)]) + +print('\nShorted net sets:') +for short in nets_info.get_shorted_nets(): + print('(' + ','.join([repr(nn) for nn in sorted(list(short))]) + ')') + +print('\nOpen nets:') +print(pformat(dict(nets_info.get_open_nets()))) diff --git a/snarl/interfaces/masque.py b/snarl/interfaces/masque.py index 8b9e3af..baa6905 100644 --- a/snarl/interfaces/masque.py +++ b/snarl/interfaces/masque.py @@ -37,8 +37,21 @@ def read_topcell( metal_labels = defaultdict(list) for label_layer, metal_layer in label_mapping.items(): - labels = [ll for ll in topcell.labels if ll.layer == label_layer] - metal_labels[metal_layer] += [(*ll.offset, ll.string) for ll in labels] + labels = [] + for ll in topcell.labels: + if ll.layer != label_layer: + continue + + if ll.repetition is None: + displacements = [(0, 0)] + else: + displacements = ll.repetition.displacements + + for displacement in displacements: + offset = ll.offset + displacement + metal_labels[metal_layer].append( + (*offset, ll.string) + ) return polys, metal_labels diff --git a/snarl/main.py b/snarl/main.py index 87839d4..a2bcfe8 100644 --- a/snarl/main.py +++ b/snarl/main.py @@ -7,10 +7,10 @@ import numpy from numpy.typing import NDArray, ArrayLike from pyclipper import scale_to_clipper, scale_from_clipper, PyPolyNode -from .types import connectivity_t, layer_t, contour_t, net_name_t +from .types import connectivity_t, layer_t, contour_t from .poly import poly_contains_points from .clipper import union_nonzero, union_evenodd, intersection_evenodd, hier2oriented -from .tracker import NetsInfo +from .tracker import NetsInfo, NetName from .utils import connectivity2layers @@ -36,7 +36,7 @@ def check_connectivity( nets_info = NetsInfo() - merge_groups: List[List[net_name_t]] = [] + merge_groups: List[List[NetName]] = [] for layer, labels_for_layer in labels.items(): point_xys = [] point_names = [] @@ -47,18 +47,18 @@ def check_connectivity( for poly in metal_polys[layer]: found_nets = label_poly(poly, point_xys, point_names, clipper_scale_factor) - name: net_name_t + name: Optional[str] if found_nets: - name = found_nets[0] + name = NetName(found_nets[0]) else: - name = object() # Anonymous net + name = NetName() # Anonymous net nets_info.get(name, layer).append(poly) if len(found_nets) > 1: # Found a short logger.warning(f'Nets {found_nets} are shorted on layer {layer} in poly:\n {pformat(poly)}') - merge_groups.append(found_nets) # type: ignore + merge_groups.append([name] + [NetName(nn) for nn in found_nets[1:]]) for group in merge_groups: first_net, *defunct_nets = group @@ -82,17 +82,6 @@ def check_connectivity( for net_a, net_b in merge_pairs: nets_info.merge(net_a, net_b) - - print('merged pairs') - print(pformat(merge_pairs)) - - print('\nFinal nets:') - print([kk for kk in nets_info.nets if isinstance(kk, str)]) - - print('\nNet sets:') - for short in nets_info.get_shorted_nets(): - print('(' + ','.join(sorted(list(short))) + ')') - return nets_info @@ -142,9 +131,9 @@ def label_poly( def find_merge_pairs( connectivity: connectivity_t, - nets: Mapping[net_name_t, Mapping[layer_t, Sequence[contour_t]]], + nets: Mapping[NetName, Mapping[layer_t, Sequence[contour_t]]], via_polys: Mapping[layer_t, Sequence[contour_t]], - ) -> Set[Tuple[net_name_t, net_name_t]]: + ) -> Set[Tuple[NetName, NetName]]: # # Merge nets based on via connectivity # @@ -155,8 +144,6 @@ def find_merge_pairs( if not vias: continue - #TODO deal with polygons that have holes (loops?) - for top_name in nets.keys(): top_polys = nets[top_name][top_layer] if not top_polys: @@ -165,7 +152,7 @@ def find_merge_pairs( for bot_name in nets.keys(): if bot_name == top_name: continue - name_pair = tuple(sorted((top_name, bot_name), key=lambda s: id(s))) + name_pair = tuple(sorted((top_name, bot_name))) if name_pair in merge_pairs: continue diff --git a/snarl/tracker.py b/snarl/tracker.py index 802d39f..ca4c27d 100644 --- a/snarl/tracker.py +++ b/snarl/tracker.py @@ -1,31 +1,61 @@ -from typing import List, Set +from typing import List, Set, ClassVar, Optional from collections import defaultdict +from dataclasses import dataclass -from .types import layer_t, net_name_t, contour_t +from .types import layer_t, contour_t + + +class NetName: + name: Optional[str] + subname: int + count: ClassVar[defaultdict[Optional[str], int]] = defaultdict(int) + + def __init__(self, name: Optional[str] = None) -> None: + self.name = name + self.subname = self.count[name] + NetName.count[name] += 1 + + def __lt__(self, other: 'NetName') -> bool: + if self.name == other.name: + return self.subname < other.subname + elif self.name is None: + return False + elif other.name is None: + return True + else: + return self.name < other.name + + def __repr__(self) -> str: + if self.name is not None: + name = self.name + else: + name = '(None)' + + if NetName.count[self.name] == 1: + return name + else: + return f'{name}__{self.subname}' class NetsInfo: - nets: defaultdict[net_name_t, defaultdict[layer_t, List]] - net_aliases: defaultdict[net_name_t, net_name_t] + nets: defaultdict[NetName, defaultdict[layer_t, List]] + net_aliases: defaultdict[NetName, NetName] def __init__(self) -> None: self.nets = defaultdict(lambda: defaultdict(list)) self.net_aliases = defaultdict(list) - def resolve_name(self, net_name: net_name_t) -> net_name_t: + def resolve_name(self, net_name: NetName) -> NetName: while net_name in self.net_aliases: net_name = self.net_aliases[net_name] return net_name - def merge(self, net_a: net_name_t, net_b: net_name_t) -> None: + def merge(self, net_a: NetName, net_b: NetName) -> None: net_a = self.resolve_name(net_a) net_b = self.resolve_name(net_b) # Always keep named nets if the other is anonymous - if not isinstance(net_a, str) and isinstance(net_b, str): - keep_net, old_net = net_b, net_a - else: - keep_net, old_net = net_a, net_b + keep_net, old_net = sorted((net_a, net_b)) #logger.info(f'merging {old_net} into {keep_net}') self.net_aliases[old_net] = keep_net @@ -34,17 +64,34 @@ class NetsInfo: self.nets[keep_net][layer] += self.nets[old_net][layer] del self.nets[old_net] - def get(self, net: net_name_t, layer: layer_t) -> List[contour_t]: + def get(self, net: NetName, layer: layer_t) -> List[contour_t]: return self.nets[self.resolve_name(net)][layer] - def get_shorted_nets(self) -> List[Set[str]]: + def get_shorted_nets(self) -> List[Set[NetName]]: shorts = defaultdict(list) for kk in self.net_aliases: - if isinstance(kk, str): - base_name = self.resolve_name(kk) - assert(isinstance(base_name, str)) - shorts[base_name].append(kk) + if kk.name is None: + continue + + base_name = self.resolve_name(kk) + assert(base_name.name is not None) + shorts[base_name].append(kk) shorted_sets = [set([kk] + others) for kk, others in shorts.items()] return shorted_sets + + def get_open_nets(self) -> defaultdict[str, List[NetName]]: + opens = defaultdict(list) + seen_names = {} + for kk in self.nets: + if kk.name is None: + continue + + if kk.name in seen_names: + if kk.name not in opens: + opens[kk.name].append(seen_names[kk.name]) + opens[kk.name].append(kk) + else: + seen_names[kk.name] = kk + return opens diff --git a/snarl/types.py b/snarl/types.py index 1acf31e..54482c4 100644 --- a/snarl/types.py +++ b/snarl/types.py @@ -2,5 +2,4 @@ from typing import Union, Tuple, List, Sequence, Optional layer_t = Tuple[int, int] contour_t = List[Tuple[int, int]] -net_name_t = Union[str, object] connectivity_t = Sequence[Tuple[layer_t, Optional[layer_t], layer_t]]