Coverage for klayout_pex/klayout/netlist_expander.py: 97%
62 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 ..common.capacitance_matrix import CapacitanceMatrix
36from ..device_models import DeviceModels
37from klayout_pex.klayout.capacitance_matrix_interpreter import CapacitanceMatrixInterpreter
38from .parasitic_device_classes import PARASITIC_CAPACITOR_CLASS_NAME
39from ..util.unit_formatter import format_spice_number
42class NetlistExpander:
43 @staticmethod
44 def expand(extracted_netlist: kdb.Netlist,
45 top_cell_name: str,
46 cap_matrix: CapacitanceMatrix,
47 cap_matrix_interpreter: CapacitanceMatrixInterpreter,
48 blackbox_devices: bool,
49 device_models: DeviceModels,
50 substrate_net_name: str) -> kdb.Netlist:
51 """
52 :param device_models: the device models of the tech info, whose metal capacitors white-box mode removes
53 :param substrate_net_name: the conductor of the substrate (see FasterCapInputBuilder),
54 a port of its own if the netlist has no such net
55 """
56 expanded_netlist: kdb.Netlist = extracted_netlist.dup()
57 top_circuit: kdb.Circuit = expanded_netlist.circuit_by_name(top_cell_name)
59 if not blackbox_devices:
60 # NOTE: only the metal capacitors (e.g. MIM and MOM caps), whose capacitance is extracted
61 # from their plates and fingers (see RCX25NetlistExpander.is_whiteboxed), not e.g. the transistors
62 metal_capacitor_class_names = device_models.metal_capacitor_class_names
63 # NOTE: Store devices before modifying container
64 devices_to_remove: List[kdb.Device] = [d for d in top_circuit.each_device()
65 if d.device_class().name in metal_capacitor_class_names]
66 for d in devices_to_remove:
67 name = d.name or d.expanded_name()
68 info(f"Removing whiteboxed device {name}")
69 top_circuit.remove_device(d)
71 # create capacitor class
72 cap = kdb.DeviceClassCapacitor()
73 cap.name = PARASITIC_CAPACITOR_CLASS_NAME
74 cap.description = "Extracted by kpex/FasterCap PEX"
75 expanded_netlist.add(cap)
77 nets: List[kdb.Net] = []
79 # build table: name -> net
80 name2net: Dict[str, kdb.Net] = {n.expanded_name(): n for n in top_circuit.each_net()}
82 signal_names = [cap_matrix_interpreter.signal_name_from_conductor_name(nc)
83 for nc in cap_matrix.conductor_names]
85 # NOTE: the substrate conductor is the substrate net, or a port of its own (e.g. a metal test pattern)
86 if substrate_net_name in signal_names and substrate_net_name not in name2net:
87 substrate_net = top_circuit.create_net(substrate_net_name)
88 top_circuit.connect_pin(top_circuit.create_pin(substrate_net_name), substrate_net)
89 name2net[substrate_net_name] = substrate_net
91 # find nets for the matrix axes
92 for nn in signal_names:
93 n = name2net.get(nn)
94 if n is None:
95 raise Exception(f"No net found with name {nn}, net names are: {list(name2net.keys())}")
96 nets.append(n)
98 cap_threshold = 0.0
100 def add_parasitic_cap(i: int,
101 j: int,
102 net1: kdb.Net,
103 net2: kdb.Net,
104 cap_value: float):
105 if cap_value > cap_threshold:
106 c: kdb.Device = top_circuit.create_device(cap, f"Cext_{i}_{j}")
107 c.connect_terminal('A', net1)
108 c.connect_terminal('B', net2)
109 c.set_parameter('C', cap_value) # Farad
110 if net1 == net2:
111 raise Exception(f"Invalid attempt to create cap {c.name} between "
112 f"same net {net1} with value format_capacitance(cap_value)")
113 else:
114 warning(f"Ignoring capacitance matrix cell [{i},{j}], "
115 f"{format_spice_number(cap_value)} is below threshold {format_spice_number(cap_threshold)}")
117 # -------------------------------------------------------------
118 # Example capacitance matrix:
119 # [C11+C12+C13 -C12 -C13]
120 # [-C21 C21+C22+C23 -C23]
121 # [-C31 -C32 C31+C32+C33]
122 # -------------------------------------------------------------
123 #
124 # - Diagonal elements m[i][i] contain the capacitance over GND (Cii),
125 # but in a sum including all the other values of the row
126 #
127 # https://www.fastfieldsolvers.com/Papers/The_Maxwell_Capacitance_Matrix_WP110301_R03.pdf
128 #
129 ground_index = signal_names.index(substrate_net_name) if substrate_net_name in signal_names else 0
131 for i in range(0, cap_matrix.dimension):
132 row = cap_matrix[i]
133 cap_ii = row[i]
134 for j in range(0, cap_matrix.dimension):
135 if i == j:
136 continue
137 cap_value = -row[j] # off-diagonals are always stored as negative values
138 cap_ii -= cap_value # subtract summands to filter out Cii
139 if j > i:
140 add_parasitic_cap(i=i, j=j,
141 net1=nets[i], net2=nets[j],
142 cap_value=cap_value)
143 # NOTE: the rest of the diagonal element (the capacitance to infinity) goes to the substrate
144 if i != ground_index:
145 add_parasitic_cap(i=i, j=i,
146 net1=nets[i], net2=nets[ground_index],
147 cap_value=cap_ii)
149 return expanded_netlist