From 891485efe26c613822507f6e61690b3df0e1043d Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Tue, 1 Aug 2023 18:02:40 -0700 Subject: [PATCH 1/2] add path_length_analysis notebook --- .gitignore | 4 + docs/_toc.yml | 2 + docs/api.rst | 2 - docs/notebooks/11_get_netlist.py | 217 +++++++ docs/notebooks/11_get_netlist_spice.py | 107 ++++ docs/notebooks/21_drc.py | 164 ++++++ docs/notebooks/path_length_analysis.py | 81 +++ gplugins/path_length_analysis/__init__.py | 7 + .../path_length_analysis.py | 545 ++++++++++++++++++ .../test_pathlength_extraction.py | 161 ++++++ 10 files changed, 1288 insertions(+), 2 deletions(-) create mode 100644 docs/notebooks/11_get_netlist.py create mode 100644 docs/notebooks/11_get_netlist_spice.py create mode 100644 docs/notebooks/21_drc.py create mode 100644 docs/notebooks/path_length_analysis.py create mode 100644 gplugins/path_length_analysis/__init__.py create mode 100644 gplugins/path_length_analysis/path_length_analysis.py create mode 100644 gplugins/path_length_analysis/test_pathlength_extraction.py diff --git a/.gitignore b/.gitignore index b29177e8..24cb1121 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,10 @@ __pycache__/ *.ipynb test-data/ _autosummary/ +gplugins/path_length_analysis/test_pathlength_reporting/ +docs/notebooks/rib_strip_pathlengths/ +docs/notebooks/rib_strip_pathlengths/ + # Distribution / packaging .Python diff --git a/docs/_toc.yml b/docs/_toc.yml index 89c5eb07..39a0b4fc 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -42,6 +42,8 @@ parts: - file: notebooks/sax_03_variability_analysis - file: notebooks/lumerical_2_interconnect - file: notebooks/12_database + - file: notebooks/20_schematic_driven_layout + - file: notebooks/path_length_analysis.py - caption: Workflows chapters: - file: workflow diff --git a/docs/api.rst b/docs/api.rst index 9e2ec806..fb9b57f9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -57,8 +57,6 @@ Mode solver Plugins MEOW - - ************************ FDTD Simulation Plugins ************************ diff --git a/docs/notebooks/11_get_netlist.py b/docs/notebooks/11_get_netlist.py new file mode 100644 index 00000000..b1e543f8 --- /dev/null +++ b/docs/notebooks/11_get_netlist.py @@ -0,0 +1,217 @@ +# # Netlist extractor YAML +# +# Any component can extract its netlist with `get_netlist` +# +# While `gf.read.from_yaml` converts a `YAML Dict` into a `Component` +# +# `get_netlist` converts `Component` into a YAML `Dict` + +# + +import gdsfactory as gf +from gdsfactory.generic_tech import get_generic_pdk +from omegaconf import OmegaConf + +gf.config.rich_output() +PDK = get_generic_pdk() +PDK.activate() +# - + +c = gf.components.mzi() +c.plot() + +c.plot_netlist() + +c = gf.components.ring_single() +c.plot() + +c.plot_netlist() + +n = c.get_netlist() + +c.write_netlist("ring.yml") + +n = OmegaConf.load("ring.yml") + +i = list(n["instances"].keys()) +i + +instance_name0 = i[0] + +n["instances"][instance_name0]["settings"] + + +# ## Instance names +# +# By default get netlist names each `instance` with the name of the `reference` +# + + +# + +@gf.cell +def mzi_with_bend_automatic_naming(): + c = gf.Component() + mzi = c.add_ref(gf.components.mzi()) + bend = c.add_ref(gf.components.bend_euler()) + bend.connect("o1", mzi.ports["o2"]) + return c + + +c = mzi_with_bend_automatic_naming() +c.plot_netlist() + + +# + +@gf.cell +def mzi_with_bend_deterministic_names_using_alias(): + c = gf.Component() + mzi = c.add_ref(gf.components.mzi(), alias="my_mzi") + bend = c.add_ref(gf.components.bend_euler(), alias="my_bend") + bend.connect("o1", mzi.ports["o2"]) + return c + + +c = mzi_with_bend_deterministic_names_using_alias() +c.plot_netlist() +# - + +c = gf.components.mzi() +c.plot() + +c = gf.components.mzi() +n = c.get_netlist() +print(c.get_netlist().keys()) + +c.plot_netlist() + +n.keys() + + +# ## warnings +# +# Lets make a connectivity **error**, for example connecting ports on the wrong layer +# + + +# + +@gf.cell +def mmi_with_bend(): + c = gf.Component() + mmi = c.add_ref(gf.components.mmi1x2(), alias="mmi") + bend = c.add_ref(gf.components.bend_euler(layer=(2, 0)), alias="bend") + bend.connect("o1", mmi.ports["o2"]) + return c + + +c = mmi_with_bend() +c.plot() +# - + +n = c.get_netlist() + +print(n["warnings"]) + +c.plot_netlist() + +# ## get_netlist_recursive +# +# When you do `get_netlist()` for a component it will only show connections for the instances that belong to that component. +# So despite havingĀ a lot of connections, it will show only the meaningful connections for that component. +# For example, a ring has a ring_coupler. If you want to dig deeper, the connections that made that ring coupler are still available. +# +# `get_netlist_recursive()` returns a recursive netlist. + +c = gf.components.ring_single() +c.plot() + +c.plot_netlist() + +c = gf.components.ring_double() +c.plot() + +c.plot_netlist() + +c = gf.components.mzit() +c.plot() + +c.plot_netlist() + +# + +coupler_lengths = [10, 20, 30] +coupler_gaps = [0.1, 0.2, 0.3] +delta_lengths = [10, 100] + +c = gf.components.mzi_lattice( + coupler_lengths=coupler_lengths, + coupler_gaps=coupler_gaps, + delta_lengths=delta_lengths, +) +c.plot() +# - + +c.plot_netlist() + +# + +coupler_lengths = [10, 20, 30, 40] +coupler_gaps = [0.1, 0.2, 0.4, 0.5] +delta_lengths = [10, 100, 200] + +c = gf.components.mzi_lattice( + coupler_lengths=coupler_lengths, + coupler_gaps=coupler_gaps, + delta_lengths=delta_lengths, +) +c.plot() +# - + +n = c.get_netlist() + +c.plot_netlist() + +n_recursive = c.get_netlist_recursive() + +n_recursive.keys() + +# ## get_netlist_flat +# +# You can also flatten the recursive netlist + +flat_netlist = c.get_netlist_flat() + +# The flat netlist contains the same keys as a regular netlist: + +flat_netlist.keys() + +# However, its instances are flattened and uniquely renamed according to hierarchy: + +flat_netlist["instances"].keys() + +# Placement information is accumulated, and connections and ports are mapped, respectively, to the ports of the unique instances or the component top level ports. This can be plotted: + +c.plot_netlist_flat(with_labels=False) # labels get cluttered + +# ## allow_multiple_connections +# +# The default `get_netlist` function (also used by default by `get_netlist_recurse` and `get_netlist_flat`) can identify more than two ports sharing the same connection through the `allow_multiple` flag. +# +# For instance, consider a resistor network with one shared node: + +# + +vdiv = gf.Component("voltageDivider") +r1 = vdiv << gf.components.resistance_sheet() +r2 = vdiv << gf.components.resistance_sheet() +r3 = vdiv << gf.get_component(gf.components.resistance_sheet).rotate() +r4 = vdiv << gf.get_component(gf.components.resistance_sheet).rotate() + +r1.connect("pad2", r2.ports["pad1"]) +r3.connect("pad1", r2.ports["pad1"], preserve_orientation=True) +r4.connect("pad2", r2.ports["pad1"], preserve_orientation=True) + +vdiv +# - + +try: + vdiv.get_netlist_flat() +except Exception as exc: + print(exc) + +vdiv.get_netlist_flat(allow_multiple=True) diff --git a/docs/notebooks/11_get_netlist_spice.py b/docs/notebooks/11_get_netlist_spice.py new file mode 100644 index 00000000..5a4ba781 --- /dev/null +++ b/docs/notebooks/11_get_netlist_spice.py @@ -0,0 +1,107 @@ +# # Netlist extractor SPICE +# +# You can also extract the SPICE netlist using klayout +# + +# + +import gdsfactory as gf + + +@gf.cell +def pads_with_routes(radius: float = 10): + """Returns 2 pads connected with metal wires.""" + + c = gf.Component() + pad = gf.components.pad() + + tl = c << pad + bl = c << pad + + tr = c << pad + br = c << pad + + tl.move((0, 300)) + br.move((500, 0)) + tr.move((500, 500)) + + ports1 = [bl.ports["e3"], tl.ports["e3"]] + ports2 = [br.ports["e1"], tr.ports["e1"]] + routes = gf.routing.get_bundle(ports1, ports2, cross_section="metal3") + + for route in routes: + c.add(route.references) + + return c + + +# - + +c = pads_with_routes(radius=100) +gdspath = c.write_gds() +c.plot() + + +# + +import kfactory as kf + +lib = kf.kcell.KCLayout() +lib.read(filename=str(gdspath)) +c = lib[0] + +l2n = kf.kdb.LayoutToNetlist(c.begin_shapes_rec(0)) +for l_idx in c.kcl.layer_indices(): + l2n.connect(l2n.make_layer(l_idx, f"layer{l_idx}")) +l2n.extract_netlist() +print(l2n.netlist().to_s()) +# - + +l2n.write_l2n("netlist_pads_correct.l2n") + + +# + +@gf.cell +def pads_with_routes_shorted(radius: float = 10): + """Returns 2 pads connected with metal wires.""" + + c = gf.Component() + pad = gf.components.pad() + + tl = c << pad + bl = c << pad + + tr = c << pad + br = c << pad + + tl.move((0, 300)) + br.move((500, 0)) + tr.move((500, 500)) + + ports1 = [bl.ports["e3"], tl.ports["e3"]] + ports2 = [br.ports["e1"], tr.ports["e1"]] + routes = gf.routing.get_bundle(ports1, ports2, cross_section="metal3") + + for route in routes: + c.add(route.references) + + route = gf.routing.get_route(bl.ports["e2"], tl.ports["e4"], cross_section="metal3") + c.add(route.references) + return c + + +c = pads_with_routes_shorted(cache=False) +gdspath = c.write_gds() +c.plot() + + +# + +import kfactory as kf + +lib = kf.kcell.KCLayout() +lib.read(filename=str(gdspath)) +c = lib[0] + +l2n = kf.kdb.LayoutToNetlist(c.begin_shapes_rec(0)) +for l_idx in c.kcl.layer_indices(): + l2n.connect(l2n.make_layer(l_idx, f"layer{l_idx}")) +l2n.extract_netlist() +print(l2n.netlist().to_s()) diff --git a/docs/notebooks/21_drc.py b/docs/notebooks/21_drc.py new file mode 100644 index 00000000..c016915d --- /dev/null +++ b/docs/notebooks/21_drc.py @@ -0,0 +1,164 @@ +# %% [markdown] +# # Klayout Design Rule Checking (DRC) +# +# Your device can be fabricated correctly when it meets the Design Rule Checks (DRC) from the foundry, you can write DRC rules from gdsfactory and customize the shortcut to run the checks in Klayout. +# +# Here are some rules explained in [repo generic DRC technology](https://github.com/klayoutmatthias/si4all) and [video](https://peertube.f-si.org/videos/watch/addc77a0-8ac7-4742-b7fb-7d24360ceb97) +# +# ![rules1](https://i.imgur.com/gNP5Npn.png) + +# %% +import gdsfactory as gf +from gdsfactory.geometry.write_drc import ( + rule_area, + rule_density, + rule_enclosing, + rule_separation, + rule_space, + rule_width, + write_drc_deck_macro, +) + +# %% +help(write_drc_deck_macro) + +# %% +rules = [ + rule_width(layer="WG", value=0.2), + rule_space(layer="WG", value=0.2), + rule_width(layer="M1", value=1), + rule_width(layer="M2", value=2), + rule_space(layer="M2", value=2), + rule_separation(layer1="HEATER", layer2="M1", value=1.0), + rule_enclosing(layer1="M1", layer2="VIAC", value=0.2), + rule_area(layer="WG", min_area_um2=0.05), + rule_density( + layer="WG", layer_floorplan="FLOORPLAN", min_density=0.5, max_density=0.6 + ), +] + +drc_rule_deck = write_drc_deck_macro( + rules=rules, + layers=gf.LAYER, + shortcut="Ctrl+Shift+D", +) + +# %% [markdown] +# Lets create some DRC errors and check them on klayout. + +# %% +import gdsfactory as gf +from gdsfactory.component import Component +from gdsfactory.typings import Float2, Layer + +layer = gf.LAYER.WG + + +@gf.cell +def width_min(size: Float2 = (0.1, 0.1)) -> Component: + return gf.components.rectangle(size=size, layer=layer) + + +@gf.cell +def area_min() -> Component: + size = (0.2, 0.2) + return gf.components.rectangle(size=size, layer=layer) + + +@gf.cell +def gap_min(gap: float = 0.1) -> Component: + c = gf.Component() + r1 = c << gf.components.rectangle(size=(1, 1), layer=layer) + r2 = c << gf.components.rectangle(size=(1, 1), layer=layer) + r1.xmax = 0 + r2.xmin = gap + return c + + +@gf.cell +def separation( + gap: float = 0.1, layer1: Layer = gf.LAYER.HEATER, layer2: Layer = gf.LAYER.M1 +) -> Component: + c = gf.Component() + r1 = c << gf.components.rectangle(size=(1, 1), layer=layer1) + r2 = c << gf.components.rectangle(size=(1, 1), layer=layer2) + r1.xmax = 0 + r2.xmin = gap + return c + + +@gf.cell +def enclosing( + enclosing: float = 0.1, layer1: Layer = gf.LAYER.VIAC, layer2: Layer = gf.LAYER.M1 +) -> Component: + """Layer1 must be enclosed by layer2 by value. + + checks if layer1 encloses (is bigger than) layer2 by value + """ + w1 = 1 + w2 = w1 + enclosing + c = gf.Component() + c << gf.components.rectangle(size=(w1, w1), layer=layer1, centered=True) + r2 = c << gf.components.rectangle(size=(w2, w2), layer=layer2, centered=True) + r2.movex(0.5) + return c + + +@gf.cell +def snapping_error(gap: float = 1e-3) -> Component: + c = gf.Component() + r1 = c << gf.components.rectangle(size=(1, 1), layer=layer) + r2 = c << gf.components.rectangle(size=(1, 1), layer=layer) + r1.xmax = 0 + r2.xmin = gap + return c + + +@gf.cell +def errors() -> Component: + components = [width_min(), gap_min(), separation(), enclosing()] + c = gf.pack(components, spacing=1.5) + c = gf.add_padding_container(c[0], layers=(gf.LAYER.FLOORPLAN,), default=5) + return c + + +c = errors() +c.show() # show in klayout +c.plot() + +# %% [markdown] +# # Klayout connectivity checks +# +# You can you can to check for component overlap and unconnected pins using klayout DRC. +# +# +# The easiest way is to write all the pins on the same layer and define the allowed pin widths. +# This will check for disconnected pins or ports with width mismatch. + +# %% +import gdsfactory.geometry.write_connectivity as wc +from gdsfactory.generic_tech import LAYER + +nm = 1e-3 + +rules = [ + wc.write_connectivity_checks(pin_widths=[0.5, 0.9, 0.45], pin_layer=LAYER.PORT) +] +script = wc.write_drc_deck_macro(rules=rules, layers=None) + + +# %% [markdown] +# You can also define the connectivity checks per section + +# %% +connectivity_checks = [ + wc.ConnectivyCheck(cross_section="strip", pin_length=1 * nm, pin_layer=(1, 10)), + wc.ConnectivyCheck( + cross_section="strip_auto_widen", pin_length=1 * nm, pin_layer=(1, 10) + ), +] +rules = [ + wc.write_connectivity_checks_per_section(connectivity_checks=connectivity_checks), + "DEVREC", +] +script = wc.write_drc_deck_macro(rules=rules, layers=None) diff --git a/docs/notebooks/path_length_analysis.py b/docs/notebooks/path_length_analysis.py new file mode 100644 index 00000000..335ce11d --- /dev/null +++ b/docs/notebooks/path_length_analysis.py @@ -0,0 +1,81 @@ +# --- +# jupyter: +# jupytext: +# cell_metadata_filter: -all +# custom_cell_magics: kql +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.11.2 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# %% [markdown] +# # Path length analysis +# +# You can use the `report_pathlenghts` functionality to get a detailed CSV report and interactive visualization about the routes in your PIC. + +# %% +import gdsfactory as gf + +xs_top = [0, 10, 20, 40, 50, 80] +pitch = 127.0 +N = len(xs_top) +xs_bottom = [(i - N / 2) * pitch for i in range(N)] +layer = (1, 0) + +top_ports = [ + gf.Port(f"top_{i}", center=(xs_top[i], 0), width=0.5, orientation=270, layer=layer) + for i in range(N) +] + +bot_ports = [ + gf.Port( + f"bot_{i}", + center=(xs_bottom[i], -300), + width=0.5, + orientation=90, + layer=layer, + ) + for i in range(N) +] + +c = gf.Component(name="connect_bundle_separation") +routes = gf.routing.get_bundle( + top_ports, bot_ports, separation=5.0, end_straight_length=100 +) +for route in routes: + c.add(route.references) + +c.plot() + +# %% [markdown] +# Let's quickly demonstrate our new cross-sections and transition component. + +# %% +from pathlib import Path + +from gplugins.path_length_analysis.path_length_analysis import report_pathlengths + +report_pathlengths( + pic=c, + result_dir=Path("rib_strip_pathlengths"), + visualize=True, +) + +# %% [markdown] +# You should see an interactive webpage like the following appear, summarizing the paths in your PIC. +# +# To the left is a stick diagram, showing all the instances and paths in your circuit (with straight lines connecting ports for simplification). +# To the right is a table of the aggregate paths from all routing components in your circuit (those with `route_info` included in their `info` dictionary). +# You will see that there is also a CSV table in the results folder which has more in-depth statistics. +# +# ![](https://i.imgur.com/HbRC3R5.png) + +# %% [markdown] +# Clicking any of the routes or checking any of the boxes should highlight the respective route in the color shown in the table to the right to help you better identify them. Hovering over any of the routes or ports will display additional information. +# ![pathlength report](images/pathlength_report_highlighted.png) diff --git a/gplugins/path_length_analysis/__init__.py b/gplugins/path_length_analysis/__init__.py new file mode 100644 index 00000000..396007ce --- /dev/null +++ b/gplugins/path_length_analysis/__init__.py @@ -0,0 +1,7 @@ +from gplugins.path_length_analysis.path_length_analysis import ( + report_pathlengths, +) + +__all__ = [ + "report_pathlengths", +] diff --git a/gplugins/path_length_analysis/path_length_analysis.py b/gplugins/path_length_analysis/path_length_analysis.py new file mode 100644 index 00000000..46464243 --- /dev/null +++ b/gplugins/path_length_analysis/path_length_analysis.py @@ -0,0 +1,545 @@ +import warnings +from pathlib import Path +from typing import Any + +import networkx as nx +import numpy as np +import pandas as pd +from bokeh.io import curdoc, output_file, show +from bokeh.layouts import row +from bokeh.models import ( + BoxSelectTool, + Circle, + ColumnDataSource, + DataTable, + HoverTool, + HTMLTemplateFormatter, + MultiLine, + NodesAndLinkedEdges, + Patches, + TableColumn, + TapTool, + WheelZoomTool, +) +from bokeh.palettes import Category10, Spectral4 +from bokeh.plotting import figure, from_networkx +from gdsfactory.component import Component, ComponentReference + +DEFAULT_CS_COLORS = { + "rib": "red", + "strip": "blue", + "r2s": "purple", + "m1": "#00FF92", + "m2": "gold", +} + + +def get_internal_netlist_attributes( + route_inst_def: dict[str, dict], route_info: dict | None, component: Component +): + if route_info: + link = _get_link_name(component) + component_name = route_inst_def["component"] + attrs = route_info + attrs["component"] = component_name + return {link: attrs} + else: + return None + + +def _get_link_name(component: Component): + ports = sorted(component.ports.keys()) + if len(ports) != 2: + raise ValueError("routing components must have two ports") + link = ":".join(ports) + return link + + +def _node_to_inst_port(node: str): + ip = node.split(",") + if len(ip) == 2: + inst, port = ip + elif len(ip) == 1: + port = ip[0] + inst = "" + else: + raise ValueError( + f"did not expect a connection name with more than one comma: {node}" + ) + return inst, port + + +def _is_scalar(val): + return isinstance(val, float) or isinstance(val, int) + + +def _expand_bbox(bbox): + if len(bbox) == 2: + bbox = [bbox[0], (bbox[0][0], bbox[1][1]), bbox[1], (bbox[1][0], bbox[0][1])] + return bbox + + +def sum_route_attrs(records): + totals = {} + for record in records: + for k, v in record.items(): + if _is_scalar(v): + if k not in totals: + totals[k] = v + else: + totals[k] += v + return totals + + +def report_pathlengths( + pic: Component, result_dir, visualize=False, component_connectivity=None +): + print(f"Reporting pathlengths for {pic.name}...") + pathlength_graph = get_edge_based_route_attr_graph( + pic, recursive=True, component_connectivity=component_connectivity + ) + route_records = get_paths(pathlength_graph) + + if route_records: + if not result_dir.is_dir(): + result_dir.mkdir() + pathlength_table_filename = result_dir / f"{pic.name}.pathlengths.csv" + + df = pd.DataFrame.from_records(route_records) + df.to_csv(pathlength_table_filename) + print(f"Success! Wrote pathlength table to {pathlength_table_filename}") + + if visualize: + visualize_graph(pic, pathlength_graph, route_records, result_dir) + + +def get_paths(pathlength_graph: nx.Graph) -> list[dict[str, Any]]: + """ + Gets a list of dictionaries from the pathlength graph describing each of the aggregate paths. + + Args: + pathlength_graph: a graph representing a circuit + """ + paths = nx.connected_components(pathlength_graph) + route_records = [] + for path in paths: + node_degrees = pathlength_graph.degree(path) + end_nodes = [n for n, deg in node_degrees if deg == 1] + end_ports = [] + for node in end_nodes: + inst, port = _node_to_inst_port(node) + end_ports.append((inst, port)) + if len(end_ports) > 1: + node_pairs = [] + for n1 in end_nodes: + for n2 in end_nodes: + if n1 != n2: + s = {n1, n2} + if s not in node_pairs: + node_pairs.append(s) + for node_pair in node_pairs: + end_nodes = list(node_pair) + + all_paths = nx.all_shortest_paths(pathlength_graph, *end_nodes) + for path in all_paths: + record = {} + record["src_inst"], record["src_port"] = end_ports[0] + record["src_node"] = end_nodes[0] + record["dst_inst"], record["dst_port"] = end_ports[1] + record["dst_node"] = end_nodes[1] + insts = [n.partition(",")[0] for n in path] + valid_path = True + for i in range(1, len(insts) - 1): + if insts[i - 1] == insts[i] == insts[i + 1]: + if i == len(insts) - 2: + path.pop() + elif i == 1: + path.pop(0) + else: + valid_path = False + break + end_nodes = [path[0], path[-1]] + end_ports2 = [] + for node in end_nodes: + inst, port = _node_to_inst_port(node) + end_ports2.append((inst, port)) + record["src_inst"], record["src_port"] = end_ports2[0] + record["src_node"] = end_nodes[0] + record["dst_inst"], record["dst_port"] = end_ports2[1] + record["dst_node"] = end_nodes[1] + if not valid_path: + continue + edges = pathlength_graph.edges(nbunch=path, data=True) + edge_data = [e[2] for e in edges if e[2]] + summed_route_attrs = sum_route_attrs(edge_data) + if "weight" in summed_route_attrs: + summed_route_attrs.pop("weight") + if summed_route_attrs: + record.update(summed_route_attrs) + route_records.append(record) + record["edges"] = edges + record["nodes"] = path + return route_records + + +def _get_subinst_node_name(node_name, inst_name): + if "," in node_name: + new_node_name = f"{inst_name}.{node_name}" + else: + # for top-level ports + new_node_name = f"{inst_name},{node_name}" + return new_node_name + + +def idealized_mxn_connectivity(inst_name: str, ref: ComponentReference, g: nx.Graph): + """ + Connects all input ports to all output ports of m x n components, with idealized routes + + Args: + inst_name: The name of the instance we are providing internal routing for. + ref: The component reference. + g: The main graph we are adding connectivity to. + Returns: + None (graph is modified in-place) + """ + warnings.warn(f"using idealized links for {inst_name} ({ref.parent.name})") + in_ports = [p for p in ref.ports if p.startswith("in")] + out_ports = [p for p in ref.ports if p.startswith("out")] + for in_port in in_ports: + for out_port in out_ports: + inst_in = f"{inst_name},{in_port}" + inst_out = f"{inst_name},{out_port}" + g.add_edge(inst_in, inst_out, weight=0.0001, component=ref.parent.name) + + +def _get_edge_based_route_attr_graph( + component: Component, + recursive=False, + component_connectivity=None, + netlist=None, + netlists=None, +): + connections = netlist["connections"] + top_level_ports = netlist["ports"] + g = nx.Graph() + inst_route_attrs = {} + + # connect all ports from connections between devices + node_attrs = {} + inst_refs = {} + + for inst_name in netlist["instances"]: + ref = component.named_references[inst_name] + inst_refs[inst_name] = ref + if "route_info" in ref.parent.info: + inst_route_attrs[inst_name] = ref.parent.info["route_info"] + for port_name, port in ref.ports.items(): + ploc = port.center + pname = f"{inst_name},{port_name}" + n_attrs = { + "x": ploc[0], + "y": ploc[1], + } + node_attrs[pname] = n_attrs + g.add_node(pname, **n_attrs) + # nx.set_node_attributes(g, node_attrs) + g.add_edges_from(connections.items(), weight=0.0001) + + # connect all internal ports for devices with connectivity defined + # currently we only do this for routing components, but could do it more generally in the future + for inst_name, inst_dict in netlist["instances"].items(): + route_info = inst_route_attrs.get(inst_name) + inst_component = component.named_references[inst_name] + route_attrs = get_internal_netlist_attributes( + inst_dict, route_info, inst_component + ) + if route_attrs: + for link, attrs in route_attrs.items(): + in_port, out_port = link.split(":") + inst_in = f"{inst_name},{in_port}" + inst_out = f"{inst_name},{out_port}" + g.add_edge(inst_in, inst_out, **attrs) + elif recursive: + sub_inst = inst_refs[inst_name] + if sub_inst.parent.name in netlists: + sub_netlist = netlists[sub_inst.parent.name] + sub_graph = _get_edge_based_route_attr_graph( + sub_inst.parent, + recursive=True, + component_connectivity=component_connectivity, + netlist=sub_netlist, + netlists=netlists, + ) + sub_edges = [] + sub_nodes = [] + for edge in sub_graph.edges(data=True): + s, e, d = edge + new_edge = [] + for node_name in [s, e]: + new_node_name = _get_subinst_node_name(node_name, inst_name) + new_edge.append(new_node_name) + new_edge.append(d) + sub_edges.append(new_edge) + for node in sub_graph.nodes(data=True): + n, d = node + new_name = _get_subinst_node_name(n, inst_name) + x = d["x"] + y = d["y"] + new_pt = sub_inst._transform_point( + np.array([x, y]), + sub_inst.origin, + sub_inst.rotation, + sub_inst.x_reflection, + ) + d["x"] = new_pt[0] + d["y"] = new_pt[1] + new_node = (new_name, d) + sub_nodes.append(new_node) + g.add_nodes_from(sub_nodes) + g.add_edges_from(sub_edges) + else: + if component_connectivity: + component_connectivity(inst_name, sub_inst, g) + else: + warnings.warn( + f"ignoring any links in {inst_name} ({sub_inst.parent.name})" + ) + + # connect all top level ports + if top_level_ports: + edges = [] + for port, sub_port in top_level_ports.items(): + p_attrs = dict(node_attrs[sub_port]) + e_attrs = {"weight": 0.0001} + edge = [port, sub_port, e_attrs] + edges.append(edge) + g.add_node(port, **p_attrs) + g.add_edges_from(edges) + return g + + +def get_edge_based_route_attr_graph( + pic: Component, recursive=False, component_connectivity=None +) -> nx.Graph: + """ + Gets a connectivity graph for the circuit, with all path attributes on edges and ports as nodes. + + Args: + pic: the pic to generate a graph from. + recursive: True to expand all hierarchy. False to only report top-level connectivity. + component_connectivity: a function to report connectivity for base components. None to treat as black boxes with no internal connectivity. + + Returns: + A NetworkX Graph + """ + from gdsfactory.get_netlist import get_netlist, get_netlist_recursive + + if recursive: + netlists = get_netlist_recursive(pic, component_suffix="", full_settings=True) + netlist = netlists[pic.name] + else: + netlist = get_netlist(pic, full_settings=True) + netlists = None + + graph = _get_edge_based_route_attr_graph( + pic, recursive, component_connectivity, netlist=netlist, netlists=netlists + ) + return graph + + +def get_pathlength_widgets( + pic: Component, + G: nx.Graph, + paths: list[dict[str, Any]], + cs_colors: dict[str, str] | None = None, + default_color: str = "#CCCCCC", +) -> dict[str, Any]: + """ + Gets a dictionary of bokeh widgets which can be used to visualize pathlength. + + Args: + pic: the component to analyze + G: the connectivity graph + paths: a list of dictionaries of path attributes + cs_colors: a dictionary mapping cross-section names to colors to use in the plot + default_color: the default color to use for unmapped cross-section types + + Returns: + A dictionary of linked bokeh widgets: the pathlength_table and the pathlength_plot + """ + inst_infos = {} + node_positions = {} + if cs_colors is None: + cs_colors = DEFAULT_CS_COLORS + for node, data in G.nodes(data=True): + node_positions[node] = (data["x"], data["y"]) + instances = pic.named_references + for inst_name, ref in instances.items(): + ref: ComponentReference + inst_info = {"bbox": ref.bbox} + inst_infos[inst_name] = inst_info + pic_bbox = pic.bbox + for port_name, port in pic.ports.items(): + p = port.center + node_positions[port_name] = (p[0], p[1]) + + edge_attrs = {} + + for start_node, end_node, edge_data in G.edges(data=True): + edge_type = edge_data.get("type") + edge_color = cs_colors.get(edge_type, default_color) + edge_attrs[(start_node, end_node)] = edge_color + edge_data["start_name"] = start_node + edge_data["end_name"] = end_node + + nx.set_edge_attributes(G, edge_attrs, "edge_color") + + plot = figure( + title=f"{pic.name} -- Connectivity Graph", + x_range=(pic_bbox[0][0], pic_bbox[1][0]), + y_range=(pic_bbox[0][1], pic_bbox[1][1]), + ) + wheel_zoom = WheelZoomTool() + plot.toolbar.active_scroll = wheel_zoom + graph_to_viz = nx.convert_node_labels_to_integers(G, label_attribute="name") + node_positions_by_int_label = { + k: node_positions[d["name"]] for k, d in graph_to_viz.nodes(data=True) + } + graph_renderer = from_networkx(graph_to_viz, node_positions_by_int_label) + graph_renderer.node_renderer.glyph = Circle(size=5, fill_color=Spectral4[0]) + graph_renderer.node_renderer.hover_glyph = Circle(size=15, fill_color=Spectral4[1]) + + graph_renderer.edge_renderer.glyph = MultiLine( + line_color="edge_color", line_alpha=0.8, line_width=5 + ) + + graph_renderer.inspection_policy = NodesAndLinkedEdges() + plot.renderers.append(graph_renderer) + + path_data = { + "src_inst": [], + "dst_inst": [], + "src_port": [], + "dst_port": [], + "xs": [], + "ys": [], + "rib_length": [], + "length": [], + "strip_length": [], + "r2s_length": [], + "m2_length": [], + "color": [], + "n_bend_90": [], + } + for i, path in enumerate(paths): + if "dst_node" in path: + pp = path["nodes"] + xs = [] + ys = [] + for n in pp: + n_data = G.nodes[n] + xs.append(n_data["x"]) + ys.append(n_data["y"]) + path_data["xs"].append(xs) + path_data["ys"].append(ys) + path_data["color"].append(Category10[10][i % 10]) + for key in path_data: + if key not in ["xs", "ys", "color"]: + path_data[key].append(path.get(key, 0.0)) + + inst_patches = {"xs": [], "ys": [], "names": [], "xl": [], "yt": []} + for inst_name, inst_info in inst_infos.items(): + bbox = np.array(_expand_bbox(inst_info["bbox"])) + xs = bbox[:, 0] + ys = bbox[:, 1] + inst_patches["xs"].append(xs) + inst_patches["ys"].append(ys) + inst_patches["names"].append(inst_name) + inst_patches["xl"].append(bbox[0][0]) + inst_patches["yt"].append(bbox[0][1]) + inst_source = ColumnDataSource(inst_patches) + inst_glyph = Patches(xs="xs", ys="ys", fill_color="blue", fill_alpha=0.05) + plot.add_glyph(inst_source, inst_glyph) + + paths_ds = ColumnDataSource(path_data) + paths_glyph = MultiLine( + xs="xs", ys="ys", line_color="black", line_width=0, line_alpha=0.0 + ) + paths_glyph_selected = MultiLine(xs="xs", ys="ys", line_color="color", line_width=8) + + paths_renderer = plot.add_glyph(paths_ds, glyph=paths_glyph) + paths_renderer.selection_glyph = paths_glyph_selected + paths_renderer.hover_glyph = MultiLine( + xs="xs", ys="ys", line_color="color", line_width=8, line_alpha=0.3 + ) + + template = """ +
+   +
+ """ + formatter = HTMLTemplateFormatter(template=template) + columns = [ + TableColumn(field="color", title="Key", formatter=formatter, width=2), + TableColumn(field="src_inst", title="Source"), + TableColumn(field="src_port", title="Port"), + TableColumn(field="dst_inst", title="Dest"), + TableColumn(field="dst_port", title="Port"), + TableColumn(field="length", title="Length"), + ] + for cs_name in cs_colors: + columns.append(TableColumn(field=f"{cs_name}_length", title=cs_name)) + columns.append(TableColumn(field="n_bend_90", title="# bend90")) + table = DataTable( + source=paths_ds, + columns=columns, + sizing_mode="stretch_height", + selectable="checkbox", + ) + hover_tool_ports = HoverTool( + tooltips=[("port", "@name"), ("x", "@x"), ("y", "@y")], + renderers=[graph_renderer.node_renderer], + ) + edge_hover_tool = HoverTool( + tooltips=[ + ("Start", "@start_name"), + ("End", "@end_name"), + ("Length", "@length"), + ], + renderers=[graph_renderer.edge_renderer], + anchor="center", + ) + plot.add_tools( + TapTool(), BoxSelectTool(), wheel_zoom, hover_tool_ports, edge_hover_tool + ) + return { + "pathlength_table": table, + "pathlength_plot": plot, + } + + +def visualize_graph( + pic: Component, + G: nx.Graph, + paths: list[dict[str, Any]], + result_dir: str | Path, + cs_colors: dict[str, str] | None = None, +) -> None: + """ + Visualizes a pathlength graph with bokeh and shows the output html. + + Args: + pic: the circuit component + G: the connectivity graph + paths: the path statistics + result_dir: the directory (name or Path) in which to store results + cs_colors: a mapping of cross-section names to colors to use in the visualization + """ + widgets = get_pathlength_widgets(pic, G, paths, cs_colors=cs_colors) + plot = widgets["pathlength_plot"] + table = widgets["pathlength_table"] + layout = row(plot, table, sizing_mode="stretch_both") + curdoc().add_root(layout) + result_dir = Path(result_dir) + output_file(result_dir / f"{pic.name}.html") + show(layout) diff --git a/gplugins/path_length_analysis/test_pathlength_extraction.py b/gplugins/path_length_analysis/test_pathlength_extraction.py new file mode 100644 index 00000000..6dfbb317 --- /dev/null +++ b/gplugins/path_length_analysis/test_pathlength_extraction.py @@ -0,0 +1,161 @@ +import pathlib + +import gdsfactory as gf +import pandas as pd +import pytest + +from gplugins.path_length_analysis.path_length_analysis import report_pathlengths + +primitive_components = ["straight", "bend_euler", "bend_circular"] +supported_cross_sections = ["rib", "strip"] + +_this_dir = pathlib.Path(__file__).parent + +results_dir = _this_dir / "test_pathlength_reporting" +if not results_dir.is_dir(): + results_dir.mkdir() + + +@pytest.mark.parametrize("component_name", primitive_components) +def test_primitives_have_route_info(component_name): + c = gf.get_component(component_name) + assert "route_info" in c.info + assert "length" in c.info + assert c.info["length"] > 0 + assert c.info["route_info"]["length"] == c.info["length"] + + +@pytest.mark.parametrize("cross_section", supported_cross_sections) +def test_pathlength_extraction(cross_section): + component_name = f"{test_pathlength_extraction.__name__}_{cross_section}" + c = gf.Component(component_name) + lengths = [10, 20, 45, 30] + expected_total_length = sum(lengths) + insts = [] + for i, length in enumerate(lengths): + inst = c << gf.get_component( + "straight", cross_section=cross_section, length=length + ) + inst.name = f"s{i}" + if insts: + inst.connect("o1", insts[-1].ports["o2"]) + insts.append(inst) + report_pathlengths(c, result_dir=results_dir) + data = pd.read_csv(results_dir / f"{component_name}.pathlengths.csv") + assert data.shape[0] == 1 + assert data["length"][0] == expected_total_length + assert data[f"{cross_section.lower()}_length"][0] == expected_total_length + src_inst = data["src_inst"][0] + dst_int = data["dst_inst"][0] + assert {src_inst, dst_int} == {"s0", f"s{len(lengths) - 1}"} + src_port = data["src_port"][0] + dst_port = data["dst_port"][0] + assert {src_port, dst_port} == {"o1", "o2"} + + +def test_multi_path_extraction(): + component_name = f"{test_multi_path_extraction.__name__}" + c = gf.Component(component_name) + lengths1 = [10, 20, 45, 30] + lengths2 = [50, 300] + expected_total_length1 = sum(lengths1) + expected_total_length2 = sum(lengths2) + insts = [] + for i, length in enumerate(lengths1): + inst = c << gf.get_component("straight", cross_section="strip", length=length) + inst.name = f"s1-{i}" + if insts: + inst.connect("o1", insts[-1].ports["o2"]) + insts.append(inst) + + insts = [] + for i, length in enumerate(lengths2): + inst = c << gf.get_component("straight", cross_section="strip", length=length) + inst.movey(-100) + inst.name = f"s2-{i}" + if insts: + inst.connect("o1", insts[-1].ports["o2"]) + insts.append(inst) + report_pathlengths(c, result_dir=results_dir) + data = pd.read_csv(results_dir / f"{component_name}.pathlengths.csv") + assert data.shape[0] == 2 + extracted_lengths = data["length"].to_list() + assert set(extracted_lengths) == {expected_total_length1, expected_total_length2} + + +@gf.cell +def pathlength_test_subckt(lengths, cross_section: str = "strip"): + c1 = gf.Component() + + insts = [] + for i, length in enumerate(lengths): + inst = c1 << gf.get_component( + "straight", cross_section=cross_section, length=length + ) + inst.name = f"s{i}" + if insts: + inst.connect("o1", insts[-1].ports["o2"]) + insts.append(inst) + c1.add_port("o1", port=insts[0].ports["o1"]) + c1.add_port("o2", port=insts[-1].ports["o2"]) + return c1 + + +def test_hierarchical_pathlength_extraction(): + component_name = f"{test_hierarchical_pathlength_extraction.__name__}" + cross_section = "strip" + c = gf.Component(component_name) + lengths = [10, 20, 45, 30] + expected_total_length_c1 = sum(lengths) + c1 = pathlength_test_subckt(lengths, cross_section) + istart = c.add_ref(c1, "istart") + imid = c.add_ref(c1, "imid") + iend = c.add_ref( + gf.get_component("straight", cross_section=cross_section, length=100), "iend" + ) + imid.connect("o1", istart.ports["o2"]) + iend.connect("o1", imid.ports["o2"]) + + expected_total_length = 2 * expected_total_length_c1 + 100 + + report_pathlengths(c, result_dir=results_dir) + data = pd.read_csv(results_dir / f"{component_name}.pathlengths.csv") + assert data.shape[0] == 1 + assert data["length"][0] == expected_total_length + assert data[f"{cross_section.lower()}_length"][0] == expected_total_length + src_inst = data["src_inst"][0] + dst_int = data["dst_inst"][0] + assert {src_inst, dst_int} == {"istart", "iend"} + src_port = data["src_port"][0] + dst_port = data["dst_port"][0] + assert {src_port, dst_port} == {"o1", "o2"} + + +def test_transformed_hierarchical_pathlength_extraction(): + component_name = f"{test_transformed_hierarchical_pathlength_extraction.__name__}" + cross_section = "strip" + c = gf.Component(component_name) + lengths = [10, 20, 45, 30] + expected_total_length_c1 = sum(lengths) + c1 = pathlength_test_subckt(lengths, cross_section) + istart = c.add_ref(c1, "istart") + imid = c.add_ref(c1, "imid") + iend = c.add_ref( + gf.get_component("straight", cross_section=cross_section, length=100), "iend" + ) + istart = istart.rotate(37) + imid.connect("o1", istart.ports["o2"]) + iend.connect("o1", imid.ports["o2"]) + + expected_total_length = 2 * expected_total_length_c1 + 100 + report_pathlengths(c, result_dir=results_dir) + data = pd.read_csv(results_dir / f"{component_name}.pathlengths.csv") + assert data.shape[0] == 1 + assert data["length"][0] == expected_total_length + assert data[f"{cross_section.lower()}_length"][0] == expected_total_length + src_inst = data["src_inst"][0] + dst_int = data["dst_inst"][0] + assert {src_inst, dst_int} == {"istart", "iend"} + src_port = data["src_port"][0] + dst_port = data["dst_port"][0] + assert {src_port, dst_port} == {"o1", "o2"} From dc7af21fdd1e501340da0ea46ee9c986872d840a Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Wed, 2 Aug 2023 13:42:11 -0700 Subject: [PATCH 2/2] remove duplicated notebooks --- docs/notebooks/11_get_netlist.py | 217 ------------------------- docs/notebooks/11_get_netlist_spice.py | 107 ------------ docs/notebooks/21_drc.py | 164 ------------------- 3 files changed, 488 deletions(-) delete mode 100644 docs/notebooks/11_get_netlist.py delete mode 100644 docs/notebooks/11_get_netlist_spice.py delete mode 100644 docs/notebooks/21_drc.py diff --git a/docs/notebooks/11_get_netlist.py b/docs/notebooks/11_get_netlist.py deleted file mode 100644 index b1e543f8..00000000 --- a/docs/notebooks/11_get_netlist.py +++ /dev/null @@ -1,217 +0,0 @@ -# # Netlist extractor YAML -# -# Any component can extract its netlist with `get_netlist` -# -# While `gf.read.from_yaml` converts a `YAML Dict` into a `Component` -# -# `get_netlist` converts `Component` into a YAML `Dict` - -# + -import gdsfactory as gf -from gdsfactory.generic_tech import get_generic_pdk -from omegaconf import OmegaConf - -gf.config.rich_output() -PDK = get_generic_pdk() -PDK.activate() -# - - -c = gf.components.mzi() -c.plot() - -c.plot_netlist() - -c = gf.components.ring_single() -c.plot() - -c.plot_netlist() - -n = c.get_netlist() - -c.write_netlist("ring.yml") - -n = OmegaConf.load("ring.yml") - -i = list(n["instances"].keys()) -i - -instance_name0 = i[0] - -n["instances"][instance_name0]["settings"] - - -# ## Instance names -# -# By default get netlist names each `instance` with the name of the `reference` -# - - -# + -@gf.cell -def mzi_with_bend_automatic_naming(): - c = gf.Component() - mzi = c.add_ref(gf.components.mzi()) - bend = c.add_ref(gf.components.bend_euler()) - bend.connect("o1", mzi.ports["o2"]) - return c - - -c = mzi_with_bend_automatic_naming() -c.plot_netlist() - - -# + -@gf.cell -def mzi_with_bend_deterministic_names_using_alias(): - c = gf.Component() - mzi = c.add_ref(gf.components.mzi(), alias="my_mzi") - bend = c.add_ref(gf.components.bend_euler(), alias="my_bend") - bend.connect("o1", mzi.ports["o2"]) - return c - - -c = mzi_with_bend_deterministic_names_using_alias() -c.plot_netlist() -# - - -c = gf.components.mzi() -c.plot() - -c = gf.components.mzi() -n = c.get_netlist() -print(c.get_netlist().keys()) - -c.plot_netlist() - -n.keys() - - -# ## warnings -# -# Lets make a connectivity **error**, for example connecting ports on the wrong layer -# - - -# + -@gf.cell -def mmi_with_bend(): - c = gf.Component() - mmi = c.add_ref(gf.components.mmi1x2(), alias="mmi") - bend = c.add_ref(gf.components.bend_euler(layer=(2, 0)), alias="bend") - bend.connect("o1", mmi.ports["o2"]) - return c - - -c = mmi_with_bend() -c.plot() -# - - -n = c.get_netlist() - -print(n["warnings"]) - -c.plot_netlist() - -# ## get_netlist_recursive -# -# When you do `get_netlist()` for a component it will only show connections for the instances that belong to that component. -# So despite havingĀ a lot of connections, it will show only the meaningful connections for that component. -# For example, a ring has a ring_coupler. If you want to dig deeper, the connections that made that ring coupler are still available. -# -# `get_netlist_recursive()` returns a recursive netlist. - -c = gf.components.ring_single() -c.plot() - -c.plot_netlist() - -c = gf.components.ring_double() -c.plot() - -c.plot_netlist() - -c = gf.components.mzit() -c.plot() - -c.plot_netlist() - -# + -coupler_lengths = [10, 20, 30] -coupler_gaps = [0.1, 0.2, 0.3] -delta_lengths = [10, 100] - -c = gf.components.mzi_lattice( - coupler_lengths=coupler_lengths, - coupler_gaps=coupler_gaps, - delta_lengths=delta_lengths, -) -c.plot() -# - - -c.plot_netlist() - -# + -coupler_lengths = [10, 20, 30, 40] -coupler_gaps = [0.1, 0.2, 0.4, 0.5] -delta_lengths = [10, 100, 200] - -c = gf.components.mzi_lattice( - coupler_lengths=coupler_lengths, - coupler_gaps=coupler_gaps, - delta_lengths=delta_lengths, -) -c.plot() -# - - -n = c.get_netlist() - -c.plot_netlist() - -n_recursive = c.get_netlist_recursive() - -n_recursive.keys() - -# ## get_netlist_flat -# -# You can also flatten the recursive netlist - -flat_netlist = c.get_netlist_flat() - -# The flat netlist contains the same keys as a regular netlist: - -flat_netlist.keys() - -# However, its instances are flattened and uniquely renamed according to hierarchy: - -flat_netlist["instances"].keys() - -# Placement information is accumulated, and connections and ports are mapped, respectively, to the ports of the unique instances or the component top level ports. This can be plotted: - -c.plot_netlist_flat(with_labels=False) # labels get cluttered - -# ## allow_multiple_connections -# -# The default `get_netlist` function (also used by default by `get_netlist_recurse` and `get_netlist_flat`) can identify more than two ports sharing the same connection through the `allow_multiple` flag. -# -# For instance, consider a resistor network with one shared node: - -# + -vdiv = gf.Component("voltageDivider") -r1 = vdiv << gf.components.resistance_sheet() -r2 = vdiv << gf.components.resistance_sheet() -r3 = vdiv << gf.get_component(gf.components.resistance_sheet).rotate() -r4 = vdiv << gf.get_component(gf.components.resistance_sheet).rotate() - -r1.connect("pad2", r2.ports["pad1"]) -r3.connect("pad1", r2.ports["pad1"], preserve_orientation=True) -r4.connect("pad2", r2.ports["pad1"], preserve_orientation=True) - -vdiv -# - - -try: - vdiv.get_netlist_flat() -except Exception as exc: - print(exc) - -vdiv.get_netlist_flat(allow_multiple=True) diff --git a/docs/notebooks/11_get_netlist_spice.py b/docs/notebooks/11_get_netlist_spice.py deleted file mode 100644 index 5a4ba781..00000000 --- a/docs/notebooks/11_get_netlist_spice.py +++ /dev/null @@ -1,107 +0,0 @@ -# # Netlist extractor SPICE -# -# You can also extract the SPICE netlist using klayout -# - -# + -import gdsfactory as gf - - -@gf.cell -def pads_with_routes(radius: float = 10): - """Returns 2 pads connected with metal wires.""" - - c = gf.Component() - pad = gf.components.pad() - - tl = c << pad - bl = c << pad - - tr = c << pad - br = c << pad - - tl.move((0, 300)) - br.move((500, 0)) - tr.move((500, 500)) - - ports1 = [bl.ports["e3"], tl.ports["e3"]] - ports2 = [br.ports["e1"], tr.ports["e1"]] - routes = gf.routing.get_bundle(ports1, ports2, cross_section="metal3") - - for route in routes: - c.add(route.references) - - return c - - -# - - -c = pads_with_routes(radius=100) -gdspath = c.write_gds() -c.plot() - - -# + -import kfactory as kf - -lib = kf.kcell.KCLayout() -lib.read(filename=str(gdspath)) -c = lib[0] - -l2n = kf.kdb.LayoutToNetlist(c.begin_shapes_rec(0)) -for l_idx in c.kcl.layer_indices(): - l2n.connect(l2n.make_layer(l_idx, f"layer{l_idx}")) -l2n.extract_netlist() -print(l2n.netlist().to_s()) -# - - -l2n.write_l2n("netlist_pads_correct.l2n") - - -# + -@gf.cell -def pads_with_routes_shorted(radius: float = 10): - """Returns 2 pads connected with metal wires.""" - - c = gf.Component() - pad = gf.components.pad() - - tl = c << pad - bl = c << pad - - tr = c << pad - br = c << pad - - tl.move((0, 300)) - br.move((500, 0)) - tr.move((500, 500)) - - ports1 = [bl.ports["e3"], tl.ports["e3"]] - ports2 = [br.ports["e1"], tr.ports["e1"]] - routes = gf.routing.get_bundle(ports1, ports2, cross_section="metal3") - - for route in routes: - c.add(route.references) - - route = gf.routing.get_route(bl.ports["e2"], tl.ports["e4"], cross_section="metal3") - c.add(route.references) - return c - - -c = pads_with_routes_shorted(cache=False) -gdspath = c.write_gds() -c.plot() - - -# + -import kfactory as kf - -lib = kf.kcell.KCLayout() -lib.read(filename=str(gdspath)) -c = lib[0] - -l2n = kf.kdb.LayoutToNetlist(c.begin_shapes_rec(0)) -for l_idx in c.kcl.layer_indices(): - l2n.connect(l2n.make_layer(l_idx, f"layer{l_idx}")) -l2n.extract_netlist() -print(l2n.netlist().to_s()) diff --git a/docs/notebooks/21_drc.py b/docs/notebooks/21_drc.py deleted file mode 100644 index c016915d..00000000 --- a/docs/notebooks/21_drc.py +++ /dev/null @@ -1,164 +0,0 @@ -# %% [markdown] -# # Klayout Design Rule Checking (DRC) -# -# Your device can be fabricated correctly when it meets the Design Rule Checks (DRC) from the foundry, you can write DRC rules from gdsfactory and customize the shortcut to run the checks in Klayout. -# -# Here are some rules explained in [repo generic DRC technology](https://github.com/klayoutmatthias/si4all) and [video](https://peertube.f-si.org/videos/watch/addc77a0-8ac7-4742-b7fb-7d24360ceb97) -# -# ![rules1](https://i.imgur.com/gNP5Npn.png) - -# %% -import gdsfactory as gf -from gdsfactory.geometry.write_drc import ( - rule_area, - rule_density, - rule_enclosing, - rule_separation, - rule_space, - rule_width, - write_drc_deck_macro, -) - -# %% -help(write_drc_deck_macro) - -# %% -rules = [ - rule_width(layer="WG", value=0.2), - rule_space(layer="WG", value=0.2), - rule_width(layer="M1", value=1), - rule_width(layer="M2", value=2), - rule_space(layer="M2", value=2), - rule_separation(layer1="HEATER", layer2="M1", value=1.0), - rule_enclosing(layer1="M1", layer2="VIAC", value=0.2), - rule_area(layer="WG", min_area_um2=0.05), - rule_density( - layer="WG", layer_floorplan="FLOORPLAN", min_density=0.5, max_density=0.6 - ), -] - -drc_rule_deck = write_drc_deck_macro( - rules=rules, - layers=gf.LAYER, - shortcut="Ctrl+Shift+D", -) - -# %% [markdown] -# Lets create some DRC errors and check them on klayout. - -# %% -import gdsfactory as gf -from gdsfactory.component import Component -from gdsfactory.typings import Float2, Layer - -layer = gf.LAYER.WG - - -@gf.cell -def width_min(size: Float2 = (0.1, 0.1)) -> Component: - return gf.components.rectangle(size=size, layer=layer) - - -@gf.cell -def area_min() -> Component: - size = (0.2, 0.2) - return gf.components.rectangle(size=size, layer=layer) - - -@gf.cell -def gap_min(gap: float = 0.1) -> Component: - c = gf.Component() - r1 = c << gf.components.rectangle(size=(1, 1), layer=layer) - r2 = c << gf.components.rectangle(size=(1, 1), layer=layer) - r1.xmax = 0 - r2.xmin = gap - return c - - -@gf.cell -def separation( - gap: float = 0.1, layer1: Layer = gf.LAYER.HEATER, layer2: Layer = gf.LAYER.M1 -) -> Component: - c = gf.Component() - r1 = c << gf.components.rectangle(size=(1, 1), layer=layer1) - r2 = c << gf.components.rectangle(size=(1, 1), layer=layer2) - r1.xmax = 0 - r2.xmin = gap - return c - - -@gf.cell -def enclosing( - enclosing: float = 0.1, layer1: Layer = gf.LAYER.VIAC, layer2: Layer = gf.LAYER.M1 -) -> Component: - """Layer1 must be enclosed by layer2 by value. - - checks if layer1 encloses (is bigger than) layer2 by value - """ - w1 = 1 - w2 = w1 + enclosing - c = gf.Component() - c << gf.components.rectangle(size=(w1, w1), layer=layer1, centered=True) - r2 = c << gf.components.rectangle(size=(w2, w2), layer=layer2, centered=True) - r2.movex(0.5) - return c - - -@gf.cell -def snapping_error(gap: float = 1e-3) -> Component: - c = gf.Component() - r1 = c << gf.components.rectangle(size=(1, 1), layer=layer) - r2 = c << gf.components.rectangle(size=(1, 1), layer=layer) - r1.xmax = 0 - r2.xmin = gap - return c - - -@gf.cell -def errors() -> Component: - components = [width_min(), gap_min(), separation(), enclosing()] - c = gf.pack(components, spacing=1.5) - c = gf.add_padding_container(c[0], layers=(gf.LAYER.FLOORPLAN,), default=5) - return c - - -c = errors() -c.show() # show in klayout -c.plot() - -# %% [markdown] -# # Klayout connectivity checks -# -# You can you can to check for component overlap and unconnected pins using klayout DRC. -# -# -# The easiest way is to write all the pins on the same layer and define the allowed pin widths. -# This will check for disconnected pins or ports with width mismatch. - -# %% -import gdsfactory.geometry.write_connectivity as wc -from gdsfactory.generic_tech import LAYER - -nm = 1e-3 - -rules = [ - wc.write_connectivity_checks(pin_widths=[0.5, 0.9, 0.45], pin_layer=LAYER.PORT) -] -script = wc.write_drc_deck_macro(rules=rules, layers=None) - - -# %% [markdown] -# You can also define the connectivity checks per section - -# %% -connectivity_checks = [ - wc.ConnectivyCheck(cross_section="strip", pin_length=1 * nm, pin_layer=(1, 10)), - wc.ConnectivyCheck( - cross_section="strip_auto_widen", pin_length=1 * nm, pin_layer=(1, 10) - ), -] -rules = [ - wc.write_connectivity_checks_per_section(connectivity_checks=connectivity_checks), - "DEVREC", -] -script = wc.write_drc_deck_macro(rules=rules, layers=None)