Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Match labels to corresponding cells in netlist #204

Merged
merged 2 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion gplugins/klayout/get_netlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@


def get_l2n(
gdspath: PathType, klayout_tech_path: PathType | None = None
gdspath: PathType,
klayout_tech_path: PathType | None = None,
include_labels: bool = True,
) -> kdb.LayoutToNetlist:
"""Get the layout to netlist object from a given GDS and klayout technology file.

Args:
gdspath: Path to the GDS file.
klayout_tech_path: Path to the klayout technology file.
include_labels: Whether to include labels in the netlist connected as individual nets.

Returns:
kdb.LayoutToNetlist: The layout to netlist object.
Expand Down Expand Up @@ -52,6 +55,8 @@ def get_l2n(
layer_connection_iter = layer_connection_iter[0] if layer_connection_iter else []
correct_layer_names = set(sum(layer_connection_iter, ()))

# label locations on the connected layers on a special layer
labels = kdb.Texts(c.begin_shapes_rec(0))
# define the layers to be extracted
for l_idx in c.kcl.layer_indexes():
layer_info = c.kcl.get_info(l_idx)
Expand All @@ -61,6 +66,10 @@ def get_l2n(
except StopIteration:
same_name_as_in_connections = next(iter(names))
l2n.connect(l2n.make_layer(l_idx, same_name_as_in_connections))
if include_labels:
l2n.connect(
l2n.make_layer(l_idx, f"{same_name_as_in_connections}_LABELS"), labels
)

for layer_a, layer_via, layer_b in (
(l2n.layer_by_name(layer) for layer in layers)
Expand All @@ -73,6 +82,7 @@ def get_l2n(
l2n.connect(layer_via, layer_b)

l2n.extract_netlist()

return l2n


Expand Down
82 changes: 63 additions & 19 deletions gplugins/klayout/plot_nets.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,76 @@
import itertools
import re
from pathlib import Path

import klayout.db as kdb
import matplotlib.pyplot as plt
import networkx as nx


def _get_subcircuit_name(subcircuit: kdb.SubCircuit) -> str:
"""Get the _cell name_ of a `SubCircuit` instance"""
return f"{subcircuit.circuit_ref().name}{subcircuit.expanded_name()}"


def netlist_to_networkx(
netlist: kdb.Netlist, fully_connected: bool = False, include_labels: bool = True
) -> nx.Graph:
"""Convert a KLayout DB `Netlist` to a networkx graph.

Args:
kdbnet: The KLayout DB `Netlist` to convert to a networkx `Graph`.
include_labels: Whether to include labels in the graph connected to corresponding cells.
"""
G = nx.Graph()
assert netlist.top_circuit_count() == 1, "Multiple top cells not yet supported"

circuit, *_ = netlist.each_circuit_top_down()

# first flatten components that won't be kept
for subcircuit in circuit.each_subcircuit():
if subcircuit.name in {"TODO"}:
circuit.flatten_subcircuit(subcircuit)

for net in circuit.each_net():
net_pins = [
_get_subcircuit_name(subcircuit_pin_ref.subcircuit())
for subcircuit_pin_ref in net.each_subcircuit_pin()
]

# Assumed lone net with only label info
if include_labels and net.expanded_name() and "," not in net.expanded_name():
G.add_edges_from(zip(net_pins, [net.name] * len(net_pins)))

if fully_connected:
G.add_edges_from(itertools.combinations(net_pins, 2))
else:
G.add_edges_from(zip(net_pins[:-1], net_pins[1:]))

return G


def plot_nets(
filepath: str | Path, fully_connected: bool = False, interactive: bool = False
filepath: str | Path,
fully_connected: bool = False,
interactive: bool = False,
include_labels: bool = True,
) -> None:
"""Plots the connectivity between the components in the GDS file.
"""Plots the connectivity between the components in the KLayout LayoutToNetlist file from :func:`~get_l2n`.

Args:
filepath: Path to the KLayout netlist file.
filepath: Path to the KLayout LayoutToNetlist file.
fully_connected: Whether to plot the graph as elements fully connected to all other ones (True) or

going through other elements (False).
interactive: Whether to plot an interactive graph with `pyvis` or not.
include_labels: Whether to include labels in the graph connected to corresponding cells.
"""
filepath = Path(filepath)
code = filepath.read_text()
names = re.findall(r"name\('([\w,]+)'\)", code)

l2n = kdb.LayoutToNetlist()
l2n.read(str(filepath))
netlist = l2n.netlist()
# Creating a graph for the connectivity
G_connectivity = nx.Graph()

# Adding nodes and edges based on names
for name_group in names:
individual_names = name_group.split(",")
if fully_connected:
G_connectivity.add_edges_from(itertools.combinations(individual_names, 2))
else:
G_connectivity.add_edges_from(
zip(individual_names[:-1], individual_names[1:])
)
G_connectivity = netlist_to_networkx(
netlist, fully_connected=fully_connected, include_labels=include_labels
)

# Plotting the graph
if interactive:
Expand Down Expand Up @@ -67,10 +105,16 @@ def plot_nets(
from gdsfactory.samples.demo.lvs import pads_correct, pads_shorted

from gplugins.common.config import PATH
from gplugins.klayout.get_netlist import get_l2n

c = pads_correct()
c = pads_shorted()
c.show()

gdspath = c.write_gds(PATH.extra / "pads.gds")

l2n = get_l2n(gdspath)
path = PATH.extra / f"{c.name}.txt"
l2n.write_l2n(str(path))

plot_nets(path)
13 changes: 11 additions & 2 deletions gplugins/klayout/tests/test_plot_nets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
from gplugins.klayout.plot_nets import plot_nets


@pytest.fixture
def klayout_netlist(tmp_path):
@pytest.fixture(scope="session")
def klayout_netlist(tmp_path) -> str:
"""Get KLayout netlist file for `pads_correct`. Cached for session scope."""
c = pads_correct()

gdspath = c.write_gds(gdsdir=tmp_path)
Expand All @@ -25,3 +26,11 @@ def test_plot_nets(klayout_netlist):
def test_plot_nets_interactive(klayout_netlist):
plot_nets(klayout_netlist, interactive=True)
assert Path("connectivity.html").exists()


def test_plot_nets_not_fully_connected(klayout_netlist):
plot_nets(klayout_netlist, fully_connected=False)


def test_plot_nets_no_labels(klayout_netlist):
plot_nets(klayout_netlist, include_labels=False)