Coverage for klayout_pex/rcx25/netlist_expander.py: 98%
97 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-02 23:32 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-02 23:32 +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.parasitic_device_classes import PARASITIC_CAPACITOR_CLASS_NAME, PARASITIC_RESISTOR_CLASS_NAME
40# the net of the capacitances to the substrate in the extraction results (TechInfo.internal_substrate_layer_name)
41SUBSTRATE = 'VSUBS'
44class RCX25NetlistExpander:
45 @staticmethod
46 def is_whiteboxed(device_class: kdb.DeviceClass, device_models: DeviceModels) -> bool:
47 """
48 :return: whether white-box mode (--blackbox n) removes the devices of the class,
49 as their capacitances are extracted from their plates and fingers (e.g. MIM and MOM caps)
51 NOTE: the kind of their device model tells, not the LVS device class:
52 e.g. sky130A reads VPP caps with 4 terminals as MOS4,
53 while the MOS capacitors and varactors are capacitor classes,
54 whose capacitance (of the gate oxide) the extraction doesn't have
55 """
56 return device_class.name in device_models.metal_capacitor_class_names
58 @staticmethod
59 def expand(extracted_netlist: kdb.Netlist,
60 top_cell_name: str,
61 extraction_results: ExtractionResults,
62 blackbox_devices: bool,
63 device_models: DeviceModels,
64 substrate_net_name: Optional[str] = None) -> kdb.Netlist:
65 """
66 :param device_models: the device models of the tech info, which tell the whiteboxed devices
67 :param substrate_net_name: the net the capacitances to the substrate go to,
68 a port of its own if the netlist has none (VSUBS if not given)
69 """
70 expanded_netlist: kdb.Netlist = extracted_netlist.dup()
71 top_circuit: kdb.Circuit = expanded_netlist.circuit_by_name(top_cell_name)
73 if not blackbox_devices:
74 # NOTE: removing a device ends the iteration over the circuit's devices,
75 # so iterate over a list of them
76 for d in list(top_circuit.each_device()):
77 if RCX25NetlistExpander.is_whiteboxed(d.device_class(), device_models):
78 info(f"Removing whiteboxed device {d.name or d.expanded_name()}")
79 top_circuit.remove_device(d)
81 # create capacitor device class
82 cap = kdb.DeviceClassCapacitor()
83 # cap.name = 'KPEX_CAP'
84 cap.name = PARASITIC_CAPACITOR_CLASS_NAME
85 cap.description = "Extracted by KPEX/2.5D"
86 expanded_netlist.add(cap)
88 # create resistor device class
89 res = kdb.DeviceClassResistor()
90 # res.name = 'KPEX_RES'
91 res.name = PARASITIC_RESISTOR_CLASS_NAME
92 res.description = "Extracted by KPEX/2.5D"
93 expanded_netlist.add(res)
95 fc_gnd_net = top_circuit.create_net('FC_GND') # create GROUND net
97 summary = extraction_results.summarize()
98 cap_items = sorted(summary.capacitances.items())
99 res_items = sorted(summary.resistances.items())
101 # build table: name -> net
102 name2net: Dict[str, kdb.Net] = {n.expanded_name(): n for n in top_circuit.each_net()}
104 def add_net_if_needed(net_name: str):
105 if net_name in name2net:
106 return
107 name2net[net_name] = top_circuit.create_net(net_name)
109 # NOTE: the capacitances to the substrate are on VSUBS (TechInfo.internal_substrate_layer_name),
110 # they go to the substrate net, rather than to a net that connects to nothing
111 if any(SUBSTRATE in (key.net1, key.net2) for key, _ in cap_items):
112 substrate_net_name = substrate_net_name or SUBSTRATE
113 if substrate_net_name not in name2net:
114 substrate_net = top_circuit.create_net(substrate_net_name)
115 top_circuit.connect_pin(top_circuit.create_pin(substrate_net_name), substrate_net)
116 name2net[substrate_net_name] = substrate_net
117 name2net[SUBSTRATE] = name2net[substrate_net_name]
119 # add additional nets for new nodes (e.g. created during R extraction of vias)
120 for key, _ in cap_items:
121 add_net_if_needed(key.net1)
122 add_net_if_needed(key.net2)
123 for key, _ in res_items:
124 add_net_if_needed(key.net1)
125 add_net_if_needed(key.net2)
127 # connect each device terminal to its node of the resistor network (#211 §6),
128 # a terminal without one stays on its net, which a node of the resistor network carries
129 for key, node_name in sorted(summary.device_terminal_nodes.items()):
130 device = top_circuit.device_by_id(key.device_id)
131 if device is None: # e.g. removed, as whiteboxed
132 continue
133 add_net_if_needed(node_name)
134 device.connect_terminal(key.terminal_id, name2net[node_name])
136 # the pin of a net of several labels (e.g. A,B) becomes a port per label, in its place (#211 §11)
137 if summary.label_ports:
138 ports: List[Tuple[str, Optional[kdb.Net]]] = []
139 for pin in top_circuit.each_pin():
140 net = top_circuit.net_for_pin(pin.id())
141 port_names = summary.label_ports.get(net.expanded_name(), None) if net else None
142 if port_names is None:
143 ports.append((pin.name(), net))
144 continue
145 for port_name in port_names:
146 add_net_if_needed(port_name)
147 ports.append((port_name, name2net[port_name]))
148 for pin in list(top_circuit.each_pin()):
149 top_circuit.remove_pin(pin.id())
150 for port_name, net in ports:
151 pin = top_circuit.create_pin(port_name)
152 if net is not None:
153 top_circuit.connect_pin(pin, net)
155 for idx, (key, cap_value_femto) in enumerate(cap_items):
156 net1 = name2net[key.net1]
157 net2 = name2net[key.net2]
158 if net1 == net2 and SUBSTRATE in (key.net1, key.net2):
159 continue # e.g. the metal of the substrate net itself (VSS), shorted
161 cap_value_farad = cap_value_femto / 1e15
163 c: kdb.Device = top_circuit.create_device(cap, f"ext_{idx+1}")
164 c.connect_terminal('A', net1)
165 c.connect_terminal('B', net2)
166 c.set_parameter('C', cap_value_farad)
167 if net1 == net2:
168 warning(f"Invalid attempt to create cap {c.name} between "
169 f"same net {net1} with value {'%.12g' % cap_value_femto} fF")
171 for idx, (key, res_value) in enumerate(res_items):
172 net1 = name2net[key.net1]
173 net2 = name2net[key.net2]
175 r: kdb.Device = top_circuit.create_device(res, f"ext_{idx+1}")
176 r.connect_terminal('A', net1)
177 r.connect_terminal('B', net2)
178 r.set_parameter('R', res_value)
179 if net1 == net2:
180 warning(f"Invalid attempt to create resistor {r.name} between "
181 f"same net {net1} with value {'%.12g' % res_value}")
183 return expanded_netlist