Coverage for klayout_pex/rcx25/netlist_expander.py: 98%
110 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024-2025 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of KPEX
7# (see https://github.com/iic-jku/klayout-pex).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
24from __future__ import annotations
26import re
27from typing import *
29import klayout.db as kdb
31from ..log import (
32 info,
33 warning,
34)
35from .extraction_results import ExtractionResults
36from ..device_models import DeviceModels
37from ..klayout.netlist_expander import remove_devices
38from ..klayout.parasitic_device_classes import PARASITIC_CAPACITOR_CLASS_NAME, PARASITIC_RESISTOR_CLASS_NAME
41# the net of the capacitances to the substrate in the extraction results (TechInfo.internal_substrate_layer_name)
42SUBSTRATE = 'VSUBS'
45class RCX25NetlistExpander:
46 @staticmethod
47 def is_whiteboxed(device_class: kdb.DeviceClass, device_models: DeviceModels) -> bool:
48 """
49 :return: whether white-box mode (--blackbox n) removes the devices of the class,
50 as their capacitances are extracted from their plates and fingers (e.g. MIM and MOM caps)
52 NOTE: the kind of their device model tells, not the LVS device class:
53 e.g. sky130A reads VPP caps with 4 terminals as MOS4,
54 while the MOS capacitors and varactors are capacitor classes,
55 whose capacitance (of the gate oxide) the extraction doesn't have
56 """
57 return device_class.name in device_models.metal_capacitor_class_names
59 @staticmethod
60 def expand(extracted_netlist: kdb.Netlist,
61 top_cell_name: str,
62 extraction_results: ExtractionResults,
63 blackbox_devices: bool,
64 device_models: DeviceModels,
65 substrate_net_name: Optional[str] = None,
66 removed_device_ids: Collection[int] = ()) -> kdb.Netlist:
67 """
68 :param device_models: the device models of the tech info, which tell the whiteboxed devices
69 :param substrate_net_name: the net the capacitances to the substrate go to (VSUBS if not given),
70 created if the netlist has none, and a port if it has no pin
71 :param removed_device_ids: the devices to leave out, as a model has them (of cut cells)
72 """
73 expanded_netlist: kdb.Netlist = extracted_netlist.dup()
74 top_circuit: kdb.Circuit = expanded_netlist.circuit_by_name(top_cell_name)
76 remove_devices(extracted_netlist.circuit_by_name(top_cell_name), top_circuit, removed_device_ids)
78 if not blackbox_devices:
79 # NOTE: removing a device ends the iteration over the circuit's devices,
80 # so iterate over a list of them
81 for d in list(top_circuit.each_device()):
82 if RCX25NetlistExpander.is_whiteboxed(d.device_class(), device_models):
83 info(f"Removing whiteboxed device {d.name or d.expanded_name()}")
84 top_circuit.remove_device(d)
86 # create capacitor device class
87 cap = kdb.DeviceClassCapacitor()
88 # cap.name = 'KPEX_CAP'
89 cap.name = PARASITIC_CAPACITOR_CLASS_NAME
90 cap.description = "Extracted by KPEX/2.5D"
91 expanded_netlist.add(cap)
93 # create resistor device class
94 res = kdb.DeviceClassResistor()
95 # res.name = 'KPEX_RES'
96 res.name = PARASITIC_RESISTOR_CLASS_NAME
97 res.description = "Extracted by KPEX/2.5D"
98 expanded_netlist.add(res)
100 fc_gnd_net = top_circuit.create_net('FC_GND') # create GROUND net
102 summary = extraction_results.summarize()
103 cap_items = sorted(summary.capacitances.items())
104 res_items = sorted(summary.resistances.items())
106 # build table: name -> net
107 name2net: Dict[str, kdb.Net] = {n.expanded_name(): n for n in top_circuit.each_net()}
109 def add_net_if_needed(net_name: str):
110 if net_name in name2net:
111 return
112 name2net[net_name] = top_circuit.create_net(net_name)
114 # NOTE: the capacitances to the substrate are on VSUBS (TechInfo.internal_substrate_layer_name),
115 # they go to the substrate net, rather than to a net that connects to nothing,
116 # and the substrate net is a port, also if the LVS netlist has it without pin
117 # (e.g. IHP's global net sub! in a metal test pattern without taps)
118 if any(SUBSTRATE in (key.net1, key.net2) for key, _ in cap_items):
119 substrate_net_name = substrate_net_name or SUBSTRATE
120 substrate_net = name2net.get(substrate_net_name)
121 if substrate_net is None:
122 substrate_net = top_circuit.create_net(substrate_net_name)
123 name2net[substrate_net_name] = substrate_net
124 if substrate_net.pin_count() == 0:
125 top_circuit.connect_pin(top_circuit.create_pin(substrate_net_name), substrate_net)
126 name2net[SUBSTRATE] = substrate_net
128 # add additional nets for new nodes (e.g. created during R extraction of vias)
129 for key, _ in cap_items:
130 add_net_if_needed(key.net1)
131 add_net_if_needed(key.net2)
132 for key, _ in res_items:
133 add_net_if_needed(key.net1)
134 add_net_if_needed(key.net2)
136 # connect each device terminal to its node of the resistor network (#211 §6),
137 # a terminal without one stays on its net, which a node of the resistor network carries
138 for key, node_name in sorted(summary.device_terminal_nodes.items()):
139 device = top_circuit.device_by_id(key.device_id)
140 if device is None: # e.g. removed, as whiteboxed
141 continue
142 add_net_if_needed(node_name)
143 device.connect_terminal(key.terminal_id, name2net[node_name])
145 # the pin of a net of several labels (e.g. A,B) becomes a port per label, in its place (#211 §11)
146 if summary.label_ports:
147 ports: List[Tuple[str, Optional[kdb.Net]]] = []
148 nets_with_pin: Set[str] = set()
149 for pin in top_circuit.each_pin():
150 net = top_circuit.net_for_pin(pin.id())
151 port_names = summary.label_ports.get(net.expanded_name(), None) if net else None
152 if port_names is None:
153 ports.append((pin.name(), net))
154 continue
155 nets_with_pin.add(net.expanded_name())
156 # NOTE: a net that only exclusion blocks give other ports stays a port, as what instantiates this
157 # connects it, also if its label has no pin shape, unless only a cut cell names it,
158 # and the substrate net also if no label names it, as its node has its capacitances
159 if pin.name() not in port_names and \
160 set(port_names) <= set(summary.exclusion_ports.get(net.expanded_name(), ())) and \
161 (net.expanded_name() == substrate_net_name
162 or not any(results.exclusions is not None
163 and results.exclusions.is_only_named_by_cut_cells(net.expanded_name())
164 for results in extraction_results.cell_extraction_results.values())):
165 ports.append((pin.name(), net))
166 for port_name in port_names:
167 add_net_if_needed(port_name)
168 ports.append((port_name, name2net[port_name]))
169 # NOTE: a net without a label has its ports at exclusion markers only
170 for net_name, port_names in sorted(summary.exclusion_ports.items()):
171 if net_name not in nets_with_pin:
172 for port_name in summary.label_ports[net_name]:
173 add_net_if_needed(port_name)
174 ports.append((port_name, name2net[port_name]))
175 for pin in list(top_circuit.each_pin()):
176 top_circuit.remove_pin(pin.id())
177 for port_name, net in ports:
178 pin = top_circuit.create_pin(port_name)
179 if net is not None:
180 top_circuit.connect_pin(pin, net)
182 for idx, (key, cap_value_femto) in enumerate(cap_items):
183 net1 = name2net[key.net1]
184 net2 = name2net[key.net2]
185 if net1 == net2 and SUBSTRATE in (key.net1, key.net2):
186 continue # e.g. the metal of the substrate net itself (VSS), shorted
188 cap_value_farad = cap_value_femto / 1e15
190 c: kdb.Device = top_circuit.create_device(cap, f"ext_{idx+1}")
191 c.connect_terminal('A', net1)
192 c.connect_terminal('B', net2)
193 c.set_parameter('C', cap_value_farad)
194 if net1 == net2:
195 warning(f"Invalid attempt to create cap {c.name} between "
196 f"same net {net1} with value {'%.12g' % cap_value_femto} fF")
198 for idx, (key, res_value) in enumerate(res_items):
199 net1 = name2net[key.net1]
200 net2 = name2net[key.net2]
202 r: kdb.Device = top_circuit.create_device(res, f"ext_{idx+1}")
203 r.connect_terminal('A', net1)
204 r.connect_terminal('B', net2)
205 r.set_parameter('R', res_value)
206 if net1 == net2:
207 warning(f"Invalid attempt to create resistor {r.name} between "
208 f"same net {net1} with value {'%.12g' % res_value}")
210 return expanded_netlist