Coverage for klayout_pex/rcx25/extraction_results.py: 75%
167 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 13:45 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 13:45 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024 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/martinjankoehler/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
25from collections import defaultdict
26from dataclasses import dataclass, field
27from typing import *
29from .r.resistor_network import MultiLayerResistanceNetwork, ViaJunction, DeviceTerminal, Conductance
30from .types import NetName, LayerName, CellName
31import klayout_pex_protobuf.process_parasitics_pb2 as process_parasitics_pb2
32from ..log import error
35@dataclass
36class NodeRegion:
37 layer_name: LayerName
38 net_name: NetName
39 cap_to_gnd: float
40 perimeter: float
41 area: float
44@dataclass(frozen=True)
45class SidewallKey:
46 layer: LayerName
47 net1: NetName
48 net2: NetName
51@dataclass
52class SidewallCap: # see Magic EdgeCap, extractInt.c L444
53 key: SidewallKey
54 cap_value: float # femto farad
55 distance: float # distance in µm
56 length: float # length in µm
57 tech_spec: process_parasitics_pb2.CapacitanceInfo.SidewallCapacitance
60@dataclass(frozen=True)
61class OverlapKey:
62 layer_top: LayerName
63 net_top: NetName
64 layer_bot: LayerName
65 net_bot: NetName
68@dataclass
69class OverlapCap:
70 key: OverlapKey
71 cap_value: float # femto farad
72 shielded_area: float # in µm^2
73 unshielded_area: float # in µm^2
74 tech_spec: process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance
77@dataclass(frozen=True)
78class SideOverlapKey:
79 layer_inside: LayerName
80 net_inside: NetName
81 layer_outside: LayerName
82 net_outside: NetName
84 def __repr__(self) -> str:
85 return f"{self.layer_inside}({self.net_inside})-"\
86 f"{self.layer_outside}({self.net_outside})"
88 def __post_init__(self):
89 if self.layer_inside is None:
90 raise ValueError("layer_inside cannot be None")
91 if self.net_inside is None:
92 raise ValueError("net_inside cannot be None")
93 if self.layer_outside is None:
94 raise ValueError("layer_outside cannot be None")
95 if self.net_outside is None:
96 raise ValueError("net_outside cannot be None")
99@dataclass
100class SideOverlapCap:
101 key: SideOverlapKey
102 cap_value: float # femto farad
104 def __str__(self) -> str:
105 return f"(Side Overlap): {self.key} = {round(self.cap_value, 6)}fF"
108@dataclass(frozen=True)
109class NetCoupleKey:
110 net1: NetName
111 net2: NetName
113 def __repr__(self) -> str:
114 return f"{self.net1}-{self.net2}"
116 def __lt__(self, other) -> bool:
117 if not isinstance(other, NetCoupleKey):
118 raise NotImplemented
119 return (self.net1.casefold(), self.net2.casefold()) < (other.net1.casefold(), other.net2.casefold())
121 def __post_init__(self):
122 if self.net1 is None:
123 raise ValueError("net1 cannot be None")
124 if self.net2 is None:
125 raise ValueError("net2 cannot be None")
127 # NOTE: we norm net names alphabetically
128 def normed(self) -> NetCoupleKey:
129 if self.net1 < self.net2:
130 return self
131 else:
132 return NetCoupleKey(self.net2, self.net1)
135@dataclass
136class ExtractionSummary:
137 capacitances: Dict[NetCoupleKey, float]
138 resistances: Dict[NetCoupleKey, float]
140 @classmethod
141 def merged(cls, summaries: List[ExtractionSummary]) -> ExtractionSummary:
142 merged_capacitances = defaultdict(float)
143 merged_resistances = defaultdict(float)
144 for s in summaries:
145 for couple_key, cap in s.capacitances.items():
146 merged_capacitances[couple_key.normed()] += cap
147 for couple_key, res in s.resistances.items():
148 merged_resistances[couple_key.normed()] += res
149 return ExtractionSummary(capacitances=merged_capacitances,
150 resistances=merged_resistances)
153@dataclass
154class CellExtractionResults:
155 cell_name: CellName
157 overlap_table: Dict[OverlapKey, List[OverlapCap]] = field(default_factory=lambda: defaultdict(list))
158 sidewall_table: Dict[SidewallKey, List[SidewallCap]] = field(default_factory=lambda: defaultdict(list))
159 sideoverlap_table: Dict[SideOverlapKey, List[SideOverlapCap]] = field(default_factory=lambda: defaultdict(list))
161 resistor_network: MultiLayerResistanceNetwork = \
162 field(default_factory=lambda: MultiLayerResistanceNetwork(resistor_networks_by_layer={}, via_resistors=[]))
164 def add_overlap_cap(self, cap: OverlapCap):
165 self.overlap_table[cap.key].append(cap)
167 def add_sidewall_cap(self, cap: SidewallCap):
168 self.sidewall_table[cap.key].append(cap)
170 def add_sideoverlap_cap(self, cap: SideOverlapCap):
171 self.sideoverlap_table[cap.key].append(cap)
173 def summarize(self) -> ExtractionSummary:
174 normalized_overlap_table: Dict[NetCoupleKey, float] = defaultdict(float)
175 for key, entries in self.overlap_table.items():
176 normalized_key = NetCoupleKey(key.net_bot, key.net_top).normed()
177 normalized_overlap_table[normalized_key] += sum((e.cap_value for e in entries))
178 overlap_summary = ExtractionSummary(capacitances=normalized_overlap_table,
179 resistances={})
181 normalized_sidewall_table: Dict[NetCoupleKey, float] = defaultdict(float)
182 for key, entries in self.sidewall_table.items():
183 normalized_key = NetCoupleKey(key.net1, key.net2).normed()
184 normalized_sidewall_table[normalized_key] += sum((e.cap_value for e in entries))
185 sidewall_summary = ExtractionSummary(capacitances=normalized_sidewall_table,
186 resistances={})
188 normalized_sideoverlap_table: Dict[NetCoupleKey, float] = defaultdict(float)
189 for key, entries in self.sideoverlap_table.items():
190 normalized_key = NetCoupleKey(key.net_inside, key.net_outside).normed()
191 normalized_sideoverlap_table[normalized_key] += sum((e.cap_value for e in entries))
192 sideoverlap_summary = ExtractionSummary(capacitances=normalized_sideoverlap_table,
193 resistances={})
195 normalized_resistance_table: Dict[NetCoupleKey, float] = defaultdict(float)
196 for via_resistor in self.resistor_network.via_resistors:
197 key1: str = ''
198 match via_resistor.bottom:
199 case None:
200 key1 = '__UNKNOWN__'
201 case ViaJunction():
202 if via_resistor.bottom.network is None: # TODO: happened for ptap cell (VSS)!
203 error(f"Bottom net is None: {via_resistor}")
204 continue
205 key1 = via_resistor.bottom.network.node_names[via_resistor.bottom.node_id]
206 case DeviceTerminal():
207 key1 = via_resistor.bottom.device_terminal.net_name
208 case _:
209 raise NotImplementedError("unexpected type")
210 if via_resistor.top.network is None:
211 error(f"Top net is None: {via_resistor}")
212 continue
213 key2 = via_resistor.top.network.node_names[via_resistor.top.node_id]
214 normalized_key = NetCoupleKey(key1, key2).normed()
215 normalized_resistance_table[normalized_key] += via_resistor.resistance
216 for layer_name, networks in self.resistor_network.resistor_networks_by_layer.items():
217 for network in networks.networks:
218 visited_resistors: Set[Conductance] = set()
219 for node_id, resistors in network.node_to_s.items():
220 node_name = network.node_names[node_id]
221 for conductance, other_node_id in resistors:
222 if conductance in visited_resistors:
223 continue # we don't want to add it twice, only once per direction!
224 visited_resistors.add(conductance)
226 other_node_name = network.node_names[other_node_id]
227 ohm = networks.layer_sheet_resistance / 1000.0 / conductance.cond
228 normalized_key = NetCoupleKey(node_name, other_node_name).normed()
229 normalized_resistance_table[normalized_key] += ohm
231 resistance_summary = ExtractionSummary(capacitances={},
232 resistances=normalized_resistance_table)
234 return ExtractionSummary.merged([
235 overlap_summary, sidewall_summary, sideoverlap_summary,
236 resistance_summary
237 ])
240@dataclass
241class ExtractionResults:
242 cell_extraction_results: Dict[CellName, CellExtractionResults] = field(default_factory=dict)
244 def summarize(self) -> ExtractionSummary:
245 subsummaries = [s.summarize() for s in self.cell_extraction_results.values()]
246 return ExtractionSummary.merged(subsummaries)