Coverage for klayout_pex/rcx25/extraction_results.py: 93%
147 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-31 20:14 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-31 20: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#
25from __future__ import annotations
26from collections import defaultdict
27from dataclasses import dataclass, field
28from typing import *
30from .types import NetName, LayerName, CellName
31from ..log import error
33import klayout_pex_protobuf.kpex.r.r_network_pb2 as r_network_pb2
34import klayout_pex_protobuf.kpex.result.pex_result_pb2 as pex_result_pb2
35import klayout_pex_protobuf.kpex.tech.process_parasitics_pb2 as process_parasitics_pb2
38@dataclass
39class NodeRegion:
40 layer_name: LayerName
41 net_name: NetName
42 cap_to_gnd: float
43 perimeter: float
44 area: float
47@dataclass(frozen=True)
48class SidewallKey:
49 layer: LayerName
50 net1: NetName
51 net2: NetName
54@dataclass
55class SidewallCap: # see Magic EdgeCap, extractInt.c L444
56 key: SidewallKey
57 cap_value: float # femto farad
58 distance: float # distance in µm
59 length: float # length in µm
60 tech_spec: process_parasitics_pb2.CapacitanceInfo.SidewallCapacitance
63@dataclass(frozen=True)
64class OverlapKey:
65 layer_top: LayerName
66 net_top: NetName
67 layer_bot: LayerName
68 net_bot: NetName
71@dataclass
72class OverlapCap:
73 key: OverlapKey
74 cap_value: float # femto farad
75 shielded_area: float # in µm^2
76 unshielded_area: float # in µm^2
77 tech_spec: process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance
80@dataclass(frozen=True)
81class SideOverlapKey:
82 layer_inside: LayerName
83 net_inside: NetName
84 layer_outside: LayerName
85 net_outside: NetName
87 def __repr__(self) -> str:
88 return f"{self.layer_inside}({self.net_inside})-"\
89 f"{self.layer_outside}({self.net_outside})"
91 def __post_init__(self):
92 if self.layer_inside is None:
93 raise ValueError("layer_inside cannot be None")
94 if self.net_inside is None:
95 raise ValueError("net_inside cannot be None")
96 if self.layer_outside is None:
97 raise ValueError("layer_outside cannot be None")
98 if self.net_outside is None:
99 raise ValueError("net_outside cannot be None")
102@dataclass
103class SideOverlapCap:
104 key: SideOverlapKey
105 cap_value: float # femto farad
107 def __str__(self) -> str:
108 return f"(Side Overlap): {self.key} = {round(self.cap_value, 6)}fF"
111@dataclass(frozen=True)
112class NetCoupleKey:
113 net1: NetName
114 net2: NetName
116 def __repr__(self) -> str:
117 return f"{self.net1}-{self.net2}"
119 def __lt__(self, other) -> bool:
120 if not isinstance(other, NetCoupleKey):
121 raise NotImplemented
122 return (self.net1.casefold(), self.net2.casefold()) < (other.net1.casefold(), other.net2.casefold())
124 def __post_init__(self):
125 if self.net1 is None:
126 raise ValueError("net1 cannot be None")
127 if self.net2 is None:
128 raise ValueError("net2 cannot be None")
130 # NOTE: we norm net names alphabetically
131 def normed(self) -> NetCoupleKey:
132 if self.net1 < self.net2:
133 return self
134 else:
135 return NetCoupleKey(self.net2, self.net1)
138@dataclass
139class ExtractionSummary:
140 capacitances: Dict[NetCoupleKey, float]
141 resistances: Dict[NetCoupleKey, float]
143 @classmethod
144 def merged(cls, summaries: List[ExtractionSummary]) -> ExtractionSummary:
145 merged_capacitances = defaultdict(float)
146 merged_resistances = defaultdict(float)
147 for s in summaries:
148 for couple_key, cap in s.capacitances.items():
149 merged_capacitances[couple_key.normed()] += cap
150 for couple_key, res in s.resistances.items():
151 merged_resistances[couple_key.normed()] += res
152 return ExtractionSummary(capacitances=merged_capacitances,
153 resistances=merged_resistances)
156@dataclass
157class CellExtractionResults:
158 cell_name: CellName
160 overlap_table: Dict[OverlapKey, List[OverlapCap]] = field(default_factory=lambda: defaultdict(list))
161 sidewall_table: Dict[SidewallKey, List[SidewallCap]] = field(default_factory=lambda: defaultdict(list))
162 sideoverlap_table: Dict[SideOverlapKey, List[SideOverlapCap]] = field(default_factory=lambda: defaultdict(list))
164 r_extraction_result: pex_result_pb2.RExtractionResult = field(default_factory=lambda: pex_result_pb2.RExtractionResult())
166 def add_overlap_cap(self, cap: OverlapCap):
167 self.overlap_table[cap.key].append(cap)
169 def add_sidewall_cap(self, cap: SidewallCap):
170 self.sidewall_table[cap.key].append(cap)
172 def add_sideoverlap_cap(self, cap: SideOverlapCap):
173 self.sideoverlap_table[cap.key].append(cap)
175 def summarize(self) -> ExtractionSummary:
176 normalized_overlap_table: Dict[NetCoupleKey, float] = defaultdict(float)
177 for key, entries in self.overlap_table.items():
178 normalized_key = NetCoupleKey(key.net_bot, key.net_top).normed()
179 normalized_overlap_table[normalized_key] += sum((e.cap_value for e in entries))
180 overlap_summary = ExtractionSummary(capacitances=normalized_overlap_table,
181 resistances={})
183 normalized_sidewall_table: Dict[NetCoupleKey, float] = defaultdict(float)
184 for key, entries in self.sidewall_table.items():
185 normalized_key = NetCoupleKey(key.net1, key.net2).normed()
186 normalized_sidewall_table[normalized_key] += sum((e.cap_value for e in entries))
187 sidewall_summary = ExtractionSummary(capacitances=normalized_sidewall_table,
188 resistances={})
190 normalized_sideoverlap_table: Dict[NetCoupleKey, float] = defaultdict(float)
191 for key, entries in self.sideoverlap_table.items():
192 normalized_key = NetCoupleKey(key.net_inside, key.net_outside).normed()
193 normalized_sideoverlap_table[normalized_key] += sum((e.cap_value for e in entries))
194 sideoverlap_summary = ExtractionSummary(capacitances=normalized_sideoverlap_table,
195 resistances={})
197 normalized_resistance_table: Dict[NetCoupleKey, float] = defaultdict(float)
199 def node_name(network: r_network_pb2.RNetwork,
200 node: r_network_pb2.RNode) -> str:
201 # NOTE: if we have an electrical short between 2 pins A and B
202 # and a parasitic resistance between the two,
203 # KLayout will call the net of both pins "A,B"
204 # but we really want the pin name as the node name
205 if not node.net_name or ',' in node.net_name:
206 # NOTE: network prefix, as node name is only unique per network
207 return f"{network.net_name}.{node.node_name}"
208 return node.net_name
210 for network in self.r_extraction_result.networks:
211 node_by_id: Dict[int, r_network_pb2.RNode] = {n.node_id: n for n in network.nodes}
212 for element in network.elements:
213 node_a = node_by_id[element.node_a.node_id]
214 node_b = node_by_id[element.node_b.node_id]
215 resistance = element.resistance
216 normalized_key = NetCoupleKey(node_name(network, node_a),
217 node_name(network, node_b)).normed()
218 normalized_resistance_table[normalized_key] += resistance
220 resistance_summary = ExtractionSummary(capacitances={},
221 resistances=normalized_resistance_table)
223 return ExtractionSummary.merged([
224 overlap_summary, sidewall_summary, sideoverlap_summary,
225 resistance_summary
226 ])
229@dataclass
230class ExtractionResults:
231 cell_extraction_results: Dict[CellName, CellExtractionResults] = field(default_factory=dict)
233 def summarize(self) -> ExtractionSummary:
234 subsummaries = [s.summarize() for s in self.cell_extraction_results.values()]
235 return ExtractionSummary.merged(subsummaries)