Coverage for klayout_pex/klayout/netlist_expander.py: 97%
71 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-17 19:08 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-17 19:08 +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 klayout_pex.klayout.capacitance_matrix_interpreter import CapacitanceMatrixInterpreter
37from ..util.unit_formatter import format_spice_number
40class NetlistExpander:
41 @staticmethod
42 def expand(extracted_netlist: kdb.Netlist,
43 top_cell_name: str,
44 cap_matrix: CapacitanceMatrix,
45 cap_matrix_interpreter: CapacitanceMatrixInterpreter,
46 blackbox_devices: bool) -> kdb.Netlist:
47 expanded_netlist: kdb.Netlist = extracted_netlist.dup()
48 top_circuit: kdb.Circuit = expanded_netlist.circuit_by_name(top_cell_name)
50 if not blackbox_devices:
51 # NOTE: Store devices before modifying container
52 devices_to_remove: List[kdb.Device] = list(top_circuit.each_device())
53 for d in devices_to_remove:
54 name = d.name or d.expanded_name()
55 info(f"Removing whiteboxed device {name}")
56 top_circuit.remove_device(d)
58 # create capacitor class
59 cap = kdb.DeviceClassCapacitor()
60 cap.name = 'PEX_CAP'
61 cap.description = "Extracted by kpex/FasterCap PEX"
62 expanded_netlist.add(cap)
64 fc_gnd_net = top_circuit.create_net('FC_GND') # create GROUND net
65 vsubs_net = top_circuit.create_net("VSUBS")
66 nets: List[kdb.Net] = []
68 # build table: name -> net
69 name2net: Dict[str, kdb.Net] = {n.expanded_name(): n for n in top_circuit.each_net()}
71 # find nets for the matrix axes
72 for nc in cap_matrix.conductor_names:
73 nn = cap_matrix_interpreter.signal_name_from_conductor_name(nc)
74 n = name2net.get(nn)
75 if n is None:
76 raise Exception(f"No net found with name {nn}, net names are: {list(name2net.keys())}")
77 nets.append(n)
79 cap_threshold = 0.0
81 def add_parasitic_cap(i: int,
82 j: int,
83 net1: kdb.Net,
84 net2: kdb.Net,
85 cap_value: float):
86 if cap_value > cap_threshold:
87 c: kdb.Device = top_circuit.create_device(cap, f"Cext_{i}_{j}")
88 c.connect_terminal('A', net1)
89 c.connect_terminal('B', net2)
90 c.set_parameter('C', cap_value) # Farad
91 if net1 == net2:
92 raise Exception(f"Invalid attempt to create cap {c.name} between "
93 f"same net {net1} with value format_capacitance(cap_value)")
94 else:
95 warning(f"Ignoring capacitance matrix cell [{i},{j}], "
96 f"{format_spice_number(cap_value)} is below threshold {format_spice_number(cap_threshold)}")
98 # -------------------------------------------------------------
99 # Example capacitance matrix:
100 # [C11+C12+C13 -C12 -C13]
101 # [-C21 C21+C22+C23 -C23]
102 # [-C31 -C32 C31+C32+C33]
103 # -------------------------------------------------------------
104 #
105 # - Diagonal elements m[i][i] contain the capacitance over GND (Cii),
106 # but in a sum including all the other values of the row
107 #
108 # https://www.fastfieldsolvers.com/Papers/The_Maxwell_Capacitance_Matrix_WP110301_R03.pdf
109 #
110 for i in range(0, cap_matrix.dimension):
111 row = cap_matrix[i]
112 cap_ii = row[i]
113 for j in range(0, cap_matrix.dimension):
114 if i == j:
115 continue
116 cap_value = -row[j] # off-diagonals are always stored as negative values
117 cap_ii -= cap_value # subtract summands to filter out Cii
118 if j > i:
119 add_parasitic_cap(i=i, j=j,
120 net1=nets[i], net2=nets[j],
121 cap_value=cap_value)
122 if i > 0:
123 add_parasitic_cap(i=i, j=i,
124 net1=nets[i], net2=nets[0],
125 cap_value=cap_ii)
127 # Short VSUBS and FC_GND together
128 # VSUBS ... substrate block
129 # FC_GND ... FasterCap's GND, i.e. the diagonal Cii elements
130 # create capacitor class
132 res = kdb.DeviceClassResistor()
133 res.name = 'PEX_RES'
134 res.description = "Extracted by kpex/FasterCap PEX"
135 expanded_netlist.add(res)
137 gnd_net = name2net.get('GND', None)
138 if not gnd_net:
139 gnd_net = top_circuit.create_net('GND') # create GROUND net
141 c: kdb.Device = top_circuit.create_device(res, f"Rext_FC_GND_GND")
142 c.connect_terminal('A', fc_gnd_net)
143 c.connect_terminal('B', gnd_net)
144 c.set_parameter('R', 0)
146 c: kdb.Device = top_circuit.create_device(res, f"Rext_VSUBS_GND")
147 c.connect_terminal('A', vsubs_net)
148 c.connect_terminal('B', gnd_net)
149 c.set_parameter('R', 0)
151 return expanded_netlist