From 8080b1d42399ecd0a245db1ffe0a627febaf4d1d Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Sat, 2 Sep 2023 15:27:18 +0200 Subject: [PATCH 01/10] add primitive check for L2N extraction --- src/kfactory/kcell.py | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/kfactory/kcell.py b/src/kfactory/kcell.py index 1e08542a..ab7c53c5 100644 --- a/src/kfactory/kcell.py +++ b/src/kfactory/kcell.py @@ -1316,34 +1316,18 @@ def ymax(self) -> int: def l2n(self, port_types: Iterable[str] = ("optical",)) -> kdb.LayoutToNetlist: l2n = kdb.LayoutToNetlist(self.name, self.kcl.dbu) + + for layer in self.kcl.netlist_layer_mapping.values(): + l2n.make_layer( + layer, + layer.name + if isinstance(layer, LayerEnum) + else f"{self.kcl.get_info(layer).layer}/" + f"{self.kcl.get_info(layer).datatype}", + ) + l2n.extract_netlist() - il = l2n.internal_layout() - - def filter_port(port: Port) -> bool: - return port.port_type in port_types - - for ci in self.called_cells(): - c = self.kcl[ci] - c.circuit(l2n, port_types=port_types) - # if il.cell(c.name) is None: - # il.create_cell(c.name) - # [ - # il.cell(c.name) - # .shapes(il.layer(c.kcl.get_info(port.layer))) - # .insert(port_polygon(port.width)) - # for port in filter(filter_port, c.ports) - # ] - self.circuit(l2n, port_types=port_types) - # if il.cell(self.name) is None: - # il.create_cell(self.name) - # [ - # il.cell(self.name) - # .shapes(il.layer(self.kcl.get_info(port.layer))) - # .insert(port_polygon(port.width)) - # for port in filter(filter_port, self.ports) - # ] - il.assign(self.kcl.layout) return l2n def circuit( @@ -1532,6 +1516,7 @@ class KCLayout(BaseModel, arbitrary_types_allowed=True, extra="allow"): factories: KCellFactories kcells: dict[int, KCell] layers: type[LayerEnum] + netlist_layer_mapping: dict[LayerEnum | int, LayerEnum | int] = Field(default={}) sparameters_path: Path | str | None interconnect_cml_path: Path | str | None constants: Constants = Field(default_factory=Constants) @@ -3442,6 +3427,7 @@ def cell( check_ports: bool = True, check_instances: bool = True, snap_ports: bool = True, + add_port_layers: bool = True, ) -> ( Callable[KCellParams, KCell] | Callable[[Callable[KCellParams, KCell]], Callable[KCellParams, KCell]] @@ -3462,6 +3448,9 @@ def cell( check_instances: Check for any complex instances. A complex instance is a an instance that has a magnification != 1 or non-90° rotation. snap_ports: Snap the centers of the ports onto the grid (only x/y, not angle). + add_port_layers: Add special layers of + [kfactory.KCLayout.netlist_layer_mapping][netlist_layer_mapping] to the + ports if the port layer is in the mapping. """ def decorator_autocell( From 1547607aa55d74099c822c2669c9633378039867 Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Sun, 3 Sep 2023 00:33:58 +0200 Subject: [PATCH 02/10] some progress; TODO: move port polygons from @cell decorator --- src/kfactory/kcell.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/kfactory/kcell.py b/src/kfactory/kcell.py index ab7c53c5..7491bde9 100644 --- a/src/kfactory/kcell.py +++ b/src/kfactory/kcell.py @@ -1315,16 +1315,25 @@ def ymax(self) -> int: return self._kdb_cell.bbox().top def l2n(self, port_types: Iterable[str] = ("optical",)) -> kdb.LayoutToNetlist: - l2n = kdb.LayoutToNetlist(self.name, self.kcl.dbu) + rsi = kdb.RecursiveShapeIterator( + self.kcl.layout, + self._kdb_cell, + list(self.kcl.netlist_layer_mapping.values()), + ) + l2n = kdb.LayoutToNetlist(rsi) + l2n.threads = config.n_threads + l2n.include_floating_subcircuits = True for layer in self.kcl.netlist_layer_mapping.values(): - l2n.make_layer( + print(f"{layer=}") + l2n_layer = l2n.make_layer( layer, layer.name if isinstance(layer, LayerEnum) else f"{self.kcl.get_info(layer).layer}/" f"{self.kcl.get_info(layer).datatype}", ) + l2n.connect(l2n_layer) l2n.extract_netlist() @@ -3526,6 +3535,35 @@ def wrapped_cell( port.dcplx_trans.disp = port._dcplx_trans.disp.to_itype( dbu ).to_dtype(dbu) + if add_port_layers: + for port in cell.ports: + if port.layer in cell.kcl.netlist_layer_mapping: + if port._trans: + edge = kdb.Edge( + kdb.Point(0, -port.width // 2), + kdb.Point(0, port.width // 2), + ) + cell.shapes( + cell.kcl.netlist_layer_mapping[port.layer] + ).insert(port.trans * edge) + if port.name: + cell.shapes( + cell.kcl.netlist_layer_mapping[port.layer] + ).insert(kdb.Text(port.name, port.trans)) + else: + dedge = kdb.DEdge( + kdb.DPoint(0, -port.d.width / 2), + kdb.DPoint(0, port.d.width / 2), + ) + cell.shapes( + cell.kcl.netlist_layer_mapping[port.layer] + ).insert(port.dcplx_trans * dedge) + if port.name: + cell.shapes( + cell.kcl.netlist_layer_mapping[port.layer] + ).insert( + kdb.DText(port.name, port.dcplx_trans.s_trans()) + ) cell._locked = True return cell From e310c5c8141cb25d3c392b51cc840f90c8db2d92 Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Wed, 13 Sep 2023 00:02:03 +0200 Subject: [PATCH 03/10] fix l2n to previous state --- src/kfactory/kcell.py | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/kfactory/kcell.py b/src/kfactory/kcell.py index 7491bde9..4577fed7 100644 --- a/src/kfactory/kcell.py +++ b/src/kfactory/kcell.py @@ -32,7 +32,7 @@ from pydantic_settings import BaseSettings from typing_extensions import ParamSpec -from . import kdb, lay +from . import kdb, lay # , rdb from .conf import config from .enclosure import ( KCellEnclosure, @@ -1315,28 +1315,18 @@ def ymax(self) -> int: return self._kdb_cell.bbox().top def l2n(self, port_types: Iterable[str] = ("optical",)) -> kdb.LayoutToNetlist: - rsi = kdb.RecursiveShapeIterator( - self.kcl.layout, - self._kdb_cell, - list(self.kcl.netlist_layer_mapping.values()), - ) - l2n = kdb.LayoutToNetlist(rsi) - l2n.threads = config.n_threads - l2n.include_floating_subcircuits = True - - for layer in self.kcl.netlist_layer_mapping.values(): - print(f"{layer=}") - l2n_layer = l2n.make_layer( - layer, - layer.name - if isinstance(layer, LayerEnum) - else f"{self.kcl.get_info(layer).layer}/" - f"{self.kcl.get_info(layer).datatype}", - ) - l2n.connect(l2n_layer) - + l2n = kdb.LayoutToNetlist(self.name, self.kcl.dbu) l2n.extract_netlist() + il = l2n.internal_layout() + def filter_port(port: Port) -> bool: + return port.port_type in port_types + + for ci in self.called_cells(): + c = self.kcl[ci] + c.circuit(l2n, port_types=port_types) + self.circuit(l2n, port_types=port_types) + il.assign(self.kcl.layout) return l2n def circuit( @@ -1351,10 +1341,7 @@ def port_filter(num_port: tuple[int, Port]) -> bool: circ = kdb.Circuit() circ.name = self.name circ.cell_index = self.cell_index() - print(self.name) - print(circ.boundary) circ.boundary = self.boundary or self.dbbox() - print(circ.boundary) inst_ports: dict[ str, dict[str, list[tuple[int, int, Instance, Port, kdb.SubCircuit]]] @@ -1470,6 +1457,8 @@ def port_filter(num_port: tuple[int, Port]) -> bool: ) netlist.add(circ) + # def connectivity_check(self, layers: list[int] = []) -> rdb. + class Constants(BaseSettings): """Constant Model class.""" From a119d7b00e7d36f9b7614a9b38795cd8c7f5df2c Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Mon, 18 Sep 2023 07:42:19 +0200 Subject: [PATCH 04/10] add connectivity_check to KCell --- pyproject.toml | 2 +- src/kfactory/__init__.py | 2 + src/kfactory/kcell.py | 333 ++++++++++++++++++++++++++++++++++++++- src/kfactory/port.py | 2 + 4 files changed, 330 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b2b408c9..87f50a80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ authors = [ {name = "gdsfactory community", email = "contact@gdsfactory.com"}, ] dependencies = [ - "klayout >= 0.28.10", + "klayout >= 0.28.12", "scipy", "ruamel.yaml", "cachetools >= 5.2.0", diff --git a/src/kfactory/__init__.py b/src/kfactory/__init__.py index ae967644..be42644d 100644 --- a/src/kfactory/__init__.py +++ b/src/kfactory/__init__.py @@ -9,6 +9,7 @@ import klayout.dbcore as kdb import klayout.lay as lay +import klayout.rdb as rdb from .kcell import ( KCell, Instance, @@ -44,6 +45,7 @@ "default_save", "kdb", "lay", + "rdb", "port", "cells", "placer", diff --git a/src/kfactory/kcell.py b/src/kfactory/kcell.py index 4577fed7..a5833f00 100644 --- a/src/kfactory/kcell.py +++ b/src/kfactory/kcell.py @@ -32,7 +32,7 @@ from pydantic_settings import BaseSettings from typing_extensions import ParamSpec -from . import kdb, lay # , rdb +from . import kdb, lay, rdb from .conf import config from .enclosure import ( KCellEnclosure, @@ -40,7 +40,7 @@ LayerEnclosureCollection, LayerSection, ) -from .port import rename_clockwise +from .port import port_polygon, rename_clockwise T = TypeVar("T") @@ -516,7 +516,7 @@ def __copy__(self) -> KCell: def add_port( self, port: Port, name: str | None = None, keep_mirror: bool = False - ) -> None: + ) -> Port: """Add an existing port. E.g. from an instance to propagate the port. Args: @@ -527,7 +527,7 @@ def add_port( """ if self._locked: raise LockedError(self) - self.ports.add_port(port=port, name=name, keep_mirror=keep_mirror) + return self.ports.add_port(port=port, name=name, keep_mirror=keep_mirror) def add_ports( self, ports: Iterable[Port], prefix: str = "", keep_mirror: bool = False @@ -1457,7 +1457,291 @@ def port_filter(num_port: tuple[int, Port]) -> bool: ) netlist.add(circ) - # def connectivity_check(self, layers: list[int] = []) -> rdb. + def connectivity_check( + self, + port_types: list[str] = [], + layers: list[int] = [], + db: rdb.ReportDatabase | None = None, + recursive: bool = True, + ) -> rdb.ReportDatabase: + """Create a ReportDatabase for port problems. + + Problems are overlapping ports that aren't aligned, more than two ports + overlapping, width mismatch, port_type mismatch. + + Args: + port_types: Filter for certain port typers + layers: Only create the report for certain layers + db: Use an existing ReportDatabase instead of creating a new one + recursive: Create the report not only for this cell, but all child cells as + well. + """ + if not db: + db = rdb.ReportDatabase(f"Connectivity Check {self.name}") + if recursive: + cc = self.called_cells() + for c in self.kcl.each_cell_bottom_up(): + if c in cc: + self.kcl[c].connectivity_check( + port_types=port_types, db=db, recursive=False + ) + db_cell = db.create_cell(self.name) + cell_ports = {} + c_cat = db.category_by_path("CellPorts") or db.create_category("CellPorts") + for port in self.ports: + if (not port_types or port.port_type in port_types) and ( + not layers or port.layer in layers + ): + it = db.create_item(db_cell, c_cat) + if port.name: + it.add_value(f"Port name: {port.name}") + if port._trans: + it.add_value( + port_polygon(port.width) + .transformed(port.trans) + .to_dtype(self.kcl.dbu) + ) + else: + it.add_value( + port_polygon(port.width) + .to_dtype(self.kcl.dbu) + .transformed(port.dcplx_trans) + ) + xy = (port.x, port.y) + if port.layer not in cell_ports: + cell_ports[port.layer] = {xy: [port]} + else: + if xy not in cell_ports[port.layer]: + cell_ports[port.layer][xy] = [port] + else: + cell_ports[port.layer][xy].append(port) + + inst_ports = {} + for inst in self.insts: + for port in inst.ports: + if (not port_types or port.port_type in port_types) and ( + not layers or port.layer in layers + ): + xy = (port.x, port.y) + if port.layer not in inst_ports: + inst_ports[port.layer] = {xy: [(port, inst.cell)]} + else: + if xy not in inst_ports[port.layer]: + inst_ports[port.layer][xy] = [(port, inst.cell)] + else: + inst_ports[port.layer][xy].append((port, inst.cell)) + + for layer, port_coord_mapping in inst_ports.items(): + if isinstance(layer, LayerEnum): + ln = layer.name + else: + li = self.kcl.get_info(layer) + ln = li.name or str(li).replace("/", "_") + lc = db.category_by_path(ln) or db.create_category(ln) + for coord, ports in port_coord_mapping.items(): + match len(ports): + case 1: + if layer in cell_ports and coord in cell_ports[layer]: + ccp = _check_cell_ports( + cell_ports[layer][coord][0], ports[0][0] + ) + if ccp & 1: + subc = db.category_by_path( + lc.path() + ".widthmismatch" + ) or db.create_category(lc, "widthmismatch") + create_port_error( + ports[0][0], + cell_ports[layer][coord][0], + ports[0][1], + self, + db, + db_cell, + subc, + self.kcl.dbu, + ) + + if ccp & 2: + subc = db.category_by_path( + lc.path() + ".anglemismatch" + ) or db.create_category(lc, "anglemismatch") + create_port_error( + ports[0][0], + cell_ports[layer][coord][0], + ports[0][1], + self, + db, + db_cell, + subc, + self.kcl.dbu, + ) + if ccp & 4: + subc = db.category_by_path( + lc.path() + ".typemismatch" + ) or db.create_category(lc, "typemismatch") + create_port_error( + ports[0][0], + cell_ports[layer][coord][0], + ports[0][1], + self, + db, + db_cell, + subc, + self.kcl.dbu, + ) + else: + subc = db.category_by_path( + lc.path() + ".orphanport" + ) or db.create_category(lc, "orphanport") + it = db.create_item(db_cell, subc) + it.add_value( + f"Port Name: {ports[0][1].name}/" + f"{ports[0][0].name or ports[0][0].trans.to_s()})" + ) + if ports[0][0]._trans: + it.add_value( + port_polygon(ports[0][0].width) + .transformed(ports[0][0]._trans) + .to_dtype(self.kcl.dbu) + ) + else: + it.add_value( + port_polygon(port.width) + .to_dtype(self.kcl.dbu) + .transformed(port.dcplx_trans) + ) + + case 2: + cip = _check_inst_ports(ports[0][0], ports[1][0]) + if cip & 1: + subc = db.category_by_path( + lc.path() + ".widthmismatch" + ) or db.create_category(lc, "widthmismatch") + create_port_error( + ports[0][0], + ports[1][0], + ports[0][1], + ports[1][1], + db, + db_cell, + subc, + self.kcl.dbu, + ) + + if cip & 2: + subc = db.category_by_path( + lc.path() + ".anglemismatch" + ) or db.create_category(lc, "anglemismatch") + create_port_error( + ports[0][0], + ports[1][0], + ports[0][1], + ports[1][1], + db, + db_cell, + subc, + self.kcl.dbu, + ) + if cip & 4: + subc = db.category_by_path( + lc.path() + ".typemismatch" + ) or db.create_category(lc, "typemismatch") + create_port_error( + ports[0][0], + ports[1][0], + ports[0][1], + ports[1][1], + db, + db_cell, + subc, + self.kcl.dbu, + ) + if layer in cell_ports and coord in cell_ports[layer]: + subc = db.category_by_path( + lc.path() + ".portoverlap" + ) or db.create_category(lc, "portoverlap") + it = db.create_item(db_cell, subc) + text = "Port Names: " + values: list[rdb.RdbItemValue] = [] + cell_port = cell_ports[layer][coord][0] + text += ( + f"{self.name}." + f"{cell_port.name or cell_port.trans.to_s()}/" + ) + if cell_port._trans: + values.append( + rdb.RdbItemValue( + port_polygon(cell_port.width) + .transformed(cell_port._trans) + .to_dtype(self.kcl.dbu) + ) + ) + else: + values.append( + rdb.RdbItemValue( + port_polygon(cell_port.width) + .to_dtype(self.kcl.dbu) + .transformed(cell_port.dcplx_trans) + ) + ) + for _port in ports: + text += ( + f"{_port[1].name}." + f"{_port[0].name or _port[0].trans.to_s()}/" + ) + + values.append( + rdb.RdbItemValue( + port_polygon(_port[0].width) + .transformed(_port[0].trans) + .to_dtype(self.kcl.dbu) + ) + ) + it.add_value(text[:-1]) + for value in values: + it.add_value(value) + + case x if x > 2: + subc = db.category_by_path( + lc.path() + ".portoverlap" + ) or db.create_category(lc, "portoverlap") + it = db.create_item(db_cell, subc) + text = "Port Names: " + values = [] + for _port in ports: + text += ( + f"{_port[1].name}." + f"{_port[0].name or _port[0].trans.to_s()}/" + ) + + values.append( + rdb.RdbItemValue( + port_polygon(_port[0].width) + .transformed(_port[0].trans) + .to_dtype(self.kcl.dbu) + ) + ) + it.add_value(text[:-1]) + for value in values: + it.add_value(value) + + return db + + +def create_port_error( + p1: Port, + p2: Port, + c1: KCell, + c2: KCell, + db: rdb.ReportDatabase, + db_cell: rdb.RdbCell, + cat: rdb.RdbCategory, + dbu: float, +) -> None: + it = db.create_item(db_cell, cat) + if p1.name and p2.name: + it.add_value(f"Port Names: {c1.name}.{p1.name}/" f"{c2.name}.{p2.name}") + it.add_value(port_polygon(p1.width).transformed(p1.trans).to_dtype(dbu)) + it.add_value(port_polygon(p2.width).transformed(p2.trans).to_dtype(dbu)) class Constants(BaseSettings): @@ -1829,7 +2113,7 @@ def read( """ if register_cells: cells = set(self.layout.cells("*")) - fn = str(Path(filename).resolve()) + fn = str(Path(filename).expanduser().resolve()) if options is None: lm = self.layout.read(fn) else: @@ -2674,7 +2958,14 @@ def hash(self) -> bytes: @overload def connect( - self, port: str | Port | None, other: Port, *, mirror: bool = False + self, + port: str | Port | None, + other: Port, + *, + mirror: bool = False, + allow_width_mismatch: bool = False, + allow_layer_mismatch: bool = False, + allow_type_mismatch: bool = False, ) -> None: ... @@ -2686,6 +2977,9 @@ def connect( other_port_name: str | None, *, mirror: bool = False, + allow_width_mismatch: bool = False, + allow_layer_mismatch: bool = False, + allow_type_mismatch: bool = False, ) -> None: ... @@ -3105,7 +3399,7 @@ def __iter__(self) -> Iterator[Port]: def add_port( self, port: Port, name: str | None = None, keep_mirror: bool = False - ) -> None: + ) -> Port: """Add a port object. Args: @@ -3124,6 +3418,7 @@ def add_port( if name is not None: _port.name = name self._ports.append(_port) + return _port def add_ports( self, ports: Iterable[Port], prefix: str = "", keep_mirror: bool = False @@ -3827,6 +4122,28 @@ def dpolygon_from_array(array: Iterable[tuple[float, float]]) -> kdb.DPolygon: return kdb.DPolygon([kdb.DPoint(int(x), int(y)) for (x, y) in array]) +def _check_inst_ports(p1: Port, p2: Port) -> int: + check_int = 0 + if p1.width != p2.width: + check_int += 1 + if p1.angle != ((p2.angle + 2) % 4): + check_int += 2 + if p1.port_type != p2.port_type: + check_int += 4 + return check_int + + +def _check_cell_ports(p1: Port, p2: Port) -> int: + check_int = 0 + if p1.width != p2.width: + check_int += 1 + if p1.angle != p2.angle: + check_int += 2 + if p1.port_type != p2.port_type: + check_int += 4 + return check_int + + __all__ = [ "KCell", "Instance", diff --git a/src/kfactory/port.py b/src/kfactory/port.py index 50b42593..25e0c2b3 100644 --- a/src/kfactory/port.py +++ b/src/kfactory/port.py @@ -203,6 +203,8 @@ def port_polygon(width: int) -> kdb.Polygon: if width in polygon_dict: return polygon_dict[width] else: + if not isinstance(width, int): + width = int(width) poly = kdb.Polygon( [ kdb.Point(0, width // 2), From 5ecf9a1982290ef2eec25db1396df4eb88f40fd9 Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Mon, 18 Sep 2023 18:04:35 +0200 Subject: [PATCH 05/10] no check for width is an integer anymore --- src/kfactory/port.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/kfactory/port.py b/src/kfactory/port.py index 25e0c2b3..50b42593 100644 --- a/src/kfactory/port.py +++ b/src/kfactory/port.py @@ -203,8 +203,6 @@ def port_polygon(width: int) -> kdb.Polygon: if width in polygon_dict: return polygon_dict[width] else: - if not isinstance(width, int): - width = int(width) poly = kdb.Polygon( [ kdb.Point(0, width // 2), From 4aa6867137f76752e36ca072f02e2de64b00f357 Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Sun, 24 Sep 2023 18:51:21 +0200 Subject: [PATCH 06/10] fix some naming stuff --- demo.gds | Bin 2892 -> 0 bytes src/kfactory/kcell.py | 139 +++++++++++++++++++++++++++++++----------- 2 files changed, 104 insertions(+), 35 deletions(-) delete mode 100644 demo.gds diff --git a/demo.gds b/demo.gds deleted file mode 100644 index 85006bad80103c622bd085fce58ae3c3f9839c87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2892 zcmbW(4`|e79LMqJ_wTt=H+4%iY~sbVo4RhlTdr-_6t}r-IcMoaG3z*Qzpb<1?vDFY zF0>FM$VxFN5|Kt3(H{(n%)qceQbC9n31yH_Mqt=qDEedT{oL<11HEH3czk*I{C>~p z`+c50o{&P74rU|wCHixB2Nx`x%6%X=n7eZB1L>gicBfpaL#Dj`gt zD0}DB$%)-8rk{>f>OvE8DmR$5&AVwvM@MDx#`zPUVJP2Z{KdH7sS!f_Erb@pix?@o z7XRV%5RT%*42{8i)cvX*WtfbQ8J6JU#Ys3DE3gr#;{7-apTKMIMVyQ8U@d-*Y^gz3 zEsQmIBi?{*n8#k+h3jwxH{n^#;jg%jX7X-rH-_+O?7(M{XS7#v5Z}OIWNq_NJc$R8 z4CWy`hsW_}JcabbWF4Jr-uw}-MtY*pr^}T{MoZPyj?Eaw+wnoX6Zha2B(rrd9>yK` z9zKqzk*xZ6NLHQwvB+xBKkEnv@NHD|ECKiS5VFY{SFv#00=T+U|`J6VT5 z`qf_Wy#FRt|K5siNPcTBl3D#f?+;VYIlLY}z=ik{vbW}AcrB)oT1~S1XCcq}t8gYx zp|AAXPfv|6$;i1d-ooE;1j%Cb<5x&#AA4;qz>kpq^|7z|5BNHMf~>8-h6j-T`S#*4 z?!--)$7MK(wMaeMDI~9^p1TS;OFn_y>B$9*BWFNkZ{!JVK<235bGZY*#RQ&2Y7|G1 z9*JE@U4k{O?=gmC6zqpOmqPV+KT?mySr_!iTETro?bbA8P4hI;5A!G<#{GB@x8pwS zL~1kVBX@-9L(a8v9;wkdgzIrH-iBGE4r3j5Vhb+8*+?(+i^zG_&mw!Ozl-G44 rdb.ReportDatabase: """Create a ReportDatabase for port problems. @@ -1475,6 +1476,8 @@ def connectivity_check( db: Use an existing ReportDatabase instead of creating a new one recursive: Create the report not only for this cell, but all child cells as well. + cell_ports: Also add a category "CellPorts" which contains all the cells + selected ports. """ if not db: db = rdb.ReportDatabase(f"Connectivity Check {self.name}") @@ -1487,34 +1490,105 @@ def connectivity_check( ) db_cell = db.create_cell(self.name) cell_ports = {} - c_cat = db.category_by_path("CellPorts") or db.create_category("CellPorts") + layer_cats: dict[int, rdb.RdbCategory] = {} + + def layer_cat(layer: int) -> rdb.RdbCategory: + if layer not in layer_cats: + if isinstance(layer, LayerEnum): + ln = layer.name + else: + li = self.kcl.get_info(layer) + ln = str(li).replace("/", "_") + layer_cats[layer] = db.category_by_path(ln) or db.create_category(ln) + return layer_cats[layer] + for port in self.ports: if (not port_types or port.port_type in port_types) and ( not layers or port.layer in layers ): - it = db.create_item(db_cell, c_cat) - if port.name: - it.add_value(f"Port name: {port.name}") + if add_cell_ports: + c_cat = db.category_by_path( + layer_cat(port.layer).path() + ".CellPorts" + ) or db.create_category(layer_cat(port.layer), "CellPorts") + it = db.create_item(db_cell, c_cat) + if port.name: + it.add_value(f"Port name: {port.name}") + if port._trans: + it.add_value( + port_polygon(port.width) + .transformed(port.trans) + .to_dtype(self.kcl.dbu) + ) + else: + it.add_value( + port_polygon(port.width) + .to_dtype(self.kcl.dbu) + .transformed(port.dcplx_trans) + ) + xy = (port.x, port.y) + if port.layer not in cell_ports: + cell_ports[port.layer] = {xy: [port]} + else: + if xy not in cell_ports[port.layer]: + cell_ports[port.layer][xy] = [port] + else: + cell_ports[port.layer][xy].append(port) + rec_it = kdb.RecursiveShapeIterator( + self.kcl.layout, + self._kdb_cell, + port.layer, + kdb.Box(2, port.width) * port.trans, + ) + edges = kdb.Region(rec_it).merge().edges().merge() + port_edge = kdb.Edge(0, port.width // 2, 0, -port.width // 2) if port._trans: + port_edge = port_edge.transformed(port.trans) + else: + port_edge = port_edge.transformed( + port.dcplx_trans.to_itrans(self.kcl.dbu) + ) + p_edges = kdb.Edges([port_edge]) + phys_overlap = p_edges & edges + if not phys_overlap.is_empty() and phys_overlap[0] != port_edge: + p_cat = db.category_by_path( + layer_cat(port.layer).path() + ".PartialPhysicalShape" + ) or db.create_category( + layer_cat(port.layer), "PartialPhysicalShape" + ) + it = db.create_item(db_cell, p_cat) + it.add_value( + "Insufficient overlap, partial overlap with polygon of" + f" {(phys_overlap[0].p1- phys_overlap[0].p2).abs()}/" + f"{port.width}" + ) it.add_value( port_polygon(port.width) .transformed(port.trans) .to_dtype(self.kcl.dbu) + if port._trans + else port_polygon(port.width) + .to_dtype(self.kcl.dbu) + .transformed(port.dcplx_trans) + ) + elif phys_overlap.is_empty(): + p_cat = db.category_by_path( + layer_cat(port.layer).path() + ".MissingPhysicalShape" + ) or db.create_category( + layer_cat(port.layer), "MissingPhysicalShape" + ) + it = db.create_item(db_cell, p_cat) + it.add_value( + f"Found no overlapping Edge with Port {port.name or str(port)}" ) - else: it.add_value( port_polygon(port.width) + .transformed(port.trans) + .to_dtype(self.kcl.dbu) + if port._trans + else port_polygon(port.width) .to_dtype(self.kcl.dbu) .transformed(port.dcplx_trans) ) - xy = (port.x, port.y) - if port.layer not in cell_ports: - cell_ports[port.layer] = {xy: [port]} - else: - if xy not in cell_ports[port.layer]: - cell_ports[port.layer][xy] = [port] - else: - cell_ports[port.layer][xy].append(port) inst_ports = {} for inst in self.insts: @@ -1532,12 +1606,7 @@ def connectivity_check( inst_ports[port.layer][xy].append((port, inst.cell)) for layer, port_coord_mapping in inst_ports.items(): - if isinstance(layer, LayerEnum): - ln = layer.name - else: - li = self.kcl.get_info(layer) - ln = li.name or str(li).replace("/", "_") - lc = db.category_by_path(ln) or db.create_category(ln) + lc = layer_cat(layer) for coord, ports in port_coord_mapping.items(): match len(ports): case 1: @@ -1547,8 +1616,8 @@ def connectivity_check( ) if ccp & 1: subc = db.category_by_path( - lc.path() + ".widthmismatch" - ) or db.create_category(lc, "widthmismatch") + lc.path() + ".WidthMismatch" + ) or db.create_category(lc, "WidthMismatch") create_port_error( ports[0][0], cell_ports[layer][coord][0], @@ -1562,8 +1631,8 @@ def connectivity_check( if ccp & 2: subc = db.category_by_path( - lc.path() + ".anglemismatch" - ) or db.create_category(lc, "anglemismatch") + lc.path() + ".AngleMismatch" + ) or db.create_category(lc, "AngleMismatch") create_port_error( ports[0][0], cell_ports[layer][coord][0], @@ -1576,8 +1645,8 @@ def connectivity_check( ) if ccp & 4: subc = db.category_by_path( - lc.path() + ".typemismatch" - ) or db.create_category(lc, "typemismatch") + lc.path() + ".TypeMismatch" + ) or db.create_category(lc, "TypeMismatch") create_port_error( ports[0][0], cell_ports[layer][coord][0], @@ -1590,12 +1659,12 @@ def connectivity_check( ) else: subc = db.category_by_path( - lc.path() + ".orphanport" - ) or db.create_category(lc, "orphanport") + lc.path() + ".OrphanPort" + ) or db.create_category(lc, "OrphanPort") it = db.create_item(db_cell, subc) it.add_value( - f"Port Name: {ports[0][1].name}/" - f"{ports[0][0].name or ports[0][0].trans.to_s()})" + f"Port Name: {ports[0][1].name}" + f"{ports[0][0].name or str(ports[0][0])})" ) if ports[0][0]._trans: it.add_value( @@ -1614,8 +1683,8 @@ def connectivity_check( cip = _check_inst_ports(ports[0][0], ports[1][0]) if cip & 1: subc = db.category_by_path( - lc.path() + ".widthmismatch" - ) or db.create_category(lc, "widthmismatch") + lc.path() + ".WidthMismatch" + ) or db.create_category(lc, "WidthMismatch") create_port_error( ports[0][0], ports[1][0], @@ -1629,8 +1698,8 @@ def connectivity_check( if cip & 2: subc = db.category_by_path( - lc.path() + ".anglemismatch" - ) or db.create_category(lc, "anglemismatch") + lc.path() + ".AngleMismatch" + ) or db.create_category(lc, "AngleMismatch") create_port_error( ports[0][0], ports[1][0], @@ -1643,8 +1712,8 @@ def connectivity_check( ) if cip & 4: subc = db.category_by_path( - lc.path() + ".typemismatch" - ) or db.create_category(lc, "typemismatch") + lc.path() + ".TypeMismatch" + ) or db.create_category(lc, "TypeMismatch") create_port_error( ports[0][0], ports[1][0], From d8626671c4ac78535898e919f8bfca2575bba8e9 Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Mon, 25 Sep 2023 14:03:30 +0200 Subject: [PATCH 07/10] add instance errors for connectivity_check --- src/kfactory/kcell.py | 69 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/kfactory/kcell.py b/src/kfactory/kcell.py index 8b2fdba0..544b3591 100644 --- a/src/kfactory/kcell.py +++ b/src/kfactory/kcell.py @@ -1464,6 +1464,7 @@ def connectivity_check( db: rdb.ReportDatabase | None = None, recursive: bool = True, add_cell_ports: bool = False, + check_layerconnectivity: bool = True, ) -> rdb.ReportDatabase: """Create a ReportDatabase for port problems. @@ -1476,8 +1477,9 @@ def connectivity_check( db: Use an existing ReportDatabase instead of creating a new one recursive: Create the report not only for this cell, but all child cells as well. - cell_ports: Also add a category "CellPorts" which contains all the cells + add_cell_ports: Also add a category "CellPorts" which contains all the cells selected ports. + check_layerconnectivity: Check whether the layer overlaps with instances. """ if not db: db = rdb.ReportDatabase(f"Connectivity Check {self.name}") @@ -1792,6 +1794,71 @@ def layer_cat(layer: int) -> rdb.RdbCategory: it.add_value(text[:-1]) for value in values: it.add_value(value) + if check_layerconnectivity: + error_region_shapes = kdb.Region() + error_region_instances = kdb.Region() + reg = kdb.Region(self.shapes(layer)) + inst_regions: dict[int, kdb.Region] = {} + inst_region = kdb.Region() + for i, inst in enumerate(self.insts): + _inst_region = kdb.Region(inst.bbox(layer)) + inst_shapes: kdb.Region | None = None + if not (inst_region & _inst_region).is_empty(): + if inst_shapes is None: + inst_shapes = kdb.Region() + shape_it = self.begin_shapes_rec_overlapping( + layer, inst.bbox(layer) + ) + shape_it.select_cells([inst.cell.cell_index()]) + shape_it.min_depth = 1 + for _it in shape_it.each(): + if _it.path()[0].inst() == inst._instance: + inst_shapes.insert( + _it.shape().polygon.transformed(_it.trans()) + ) + + for j, _reg in inst_regions.items(): + if _reg & _inst_region: + __reg = kdb.Region() + shape_it = self.begin_shapes_rec_touching( + layer, (_reg & _inst_region).bbox() + ) + shape_it.select_cells([self.insts[j].cell.cell_index()]) + shape_it.min_depth = 1 + for _it in shape_it.each(): + if _it.path()[0].inst() == self.insts[j]._instance: + __reg.insert( + _it.shape().polygon.transformed(_it.trans()) + ) + + error_region_instances.insert(__reg & inst_shapes) + + if not (_inst_region & reg).is_empty(): + rec_it = self.begin_shapes_rec_touching( + layer, (_inst_region & reg).bbox() + ) + rec_it.min_depth = 1 + error_region_shapes += kdb.Region(rec_it) & reg + inst_region += _inst_region + inst_regions[i] = _inst_region + if not error_region_shapes.is_empty(): + sc = db.category_by_path( + layer_cat(layer).path() + ".ShapeInstanceOverlap" + ) or db.create_category(layer_cat(layer), "ShapeInstanceOverlap") + it = db.create_item(db_cell, sc) + it.add_value("Shapes overlapping with shapes of instances") + for poly in error_region_shapes.each(): + it.add_value(poly.to_dtype(self.kcl.dbu)) + if not error_region_instances.is_empty(): + sc = db.category_by_path( + layer_cat(layer).path() + ".ShapeInstanceOverlap" + ) or db.create_category(layer_cat(layer), "ShapeInstanceOverlap") + it = db.create_item(db_cell, sc) + it.add_value( + "Instance shapes overlapping with shapes of other instances" + ) + for poly in error_region_instances.each(): + it.add_value(poly.to_dtype(self.kcl.dbu)) return db From c0bf694fe3b0da523b9ffe8610eb14ba14417e04 Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Mon, 25 Sep 2023 14:46:47 +0200 Subject: [PATCH 08/10] Merge the shape error regions and rename them --- src/kfactory/kcell.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/kfactory/kcell.py b/src/kfactory/kcell.py index 544b3591..f2d41192 100644 --- a/src/kfactory/kcell.py +++ b/src/kfactory/kcell.py @@ -1843,21 +1843,23 @@ def layer_cat(layer: int) -> rdb.RdbCategory: inst_regions[i] = _inst_region if not error_region_shapes.is_empty(): sc = db.category_by_path( - layer_cat(layer).path() + ".ShapeInstanceOverlap" - ) or db.create_category(layer_cat(layer), "ShapeInstanceOverlap") + layer_cat(layer).path() + ".ShapeInstanceshapeOverlap" + ) or db.create_category( + layer_cat(layer), "ShapeInstanceshapeOverlap" + ) it = db.create_item(db_cell, sc) it.add_value("Shapes overlapping with shapes of instances") - for poly in error_region_shapes.each(): + for poly in error_region_shapes.merge().each(): it.add_value(poly.to_dtype(self.kcl.dbu)) if not error_region_instances.is_empty(): sc = db.category_by_path( - layer_cat(layer).path() + ".ShapeInstanceOverlap" - ) or db.create_category(layer_cat(layer), "ShapeInstanceOverlap") + layer_cat(layer).path() + ".InstanceshapeOverlap" + ) or db.create_category(layer_cat(layer), "InstanceshapeOverlap") it = db.create_item(db_cell, sc) it.add_value( "Instance shapes overlapping with shapes of other instances" ) - for poly in error_region_instances.each(): + for poly in error_region_instances.merge().each(): it.add_value(poly.to_dtype(self.kcl.dbu)) return db From b76a613d935940c99cf44534ee296c6ef2e1e399 Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Mon, 25 Sep 2023 14:56:15 +0200 Subject: [PATCH 09/10] towncrier --- changelog.d/+ffc15dfb.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/+ffc15dfb.added.md diff --git a/changelog.d/+ffc15dfb.added.md b/changelog.d/+ffc15dfb.added.md new file mode 100644 index 00000000..0e05829f --- /dev/null +++ b/changelog.d/+ffc15dfb.added.md @@ -0,0 +1 @@ +Added KCell.connectivity_chek to check for port alignments and overlaps \ No newline at end of file From 23b83ac13f0e715d98eda4423ea2bb7c870296c6 Mon Sep 17 00:00:00 2001 From: Sebastian Goeldi Date: Mon, 25 Sep 2023 15:07:19 +0200 Subject: [PATCH 10/10] fix some spelling --- src/kfactory/kcell.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/kfactory/kcell.py b/src/kfactory/kcell.py index f2d41192..622a5c07 100644 --- a/src/kfactory/kcell.py +++ b/src/kfactory/kcell.py @@ -1315,6 +1315,11 @@ def ymax(self) -> int: return self._kdb_cell.bbox().top def l2n(self, port_types: Iterable[str] = ("optical",)) -> kdb.LayoutToNetlist: + """Generate a LayoutToNetlist object from the port types. + + Args: + port_types: The port types to consider for the netlist extraction. + """ l2n = kdb.LayoutToNetlist(self.name, self.kcl.dbu) l2n.extract_netlist() il = l2n.internal_layout() @@ -1464,7 +1469,7 @@ def connectivity_check( db: rdb.ReportDatabase | None = None, recursive: bool = True, add_cell_ports: bool = False, - check_layerconnectivity: bool = True, + check_layer_connectivity: bool = True, ) -> rdb.ReportDatabase: """Create a ReportDatabase for port problems. @@ -1479,7 +1484,7 @@ def connectivity_check( well. add_cell_ports: Also add a category "CellPorts" which contains all the cells selected ports. - check_layerconnectivity: Check whether the layer overlaps with instances. + check_layer_connectivity: Check whether the layer overlaps with instances. """ if not db: db = rdb.ReportDatabase(f"Connectivity Check {self.name}") @@ -1794,7 +1799,7 @@ def layer_cat(layer: int) -> rdb.RdbCategory: it.add_value(text[:-1]) for value in values: it.add_value(value) - if check_layerconnectivity: + if check_layer_connectivity: error_region_shapes = kdb.Region() error_region_instances = kdb.Region() reg = kdb.Region(self.shapes(layer))