Coverage for klayout_pex/rcx25/extraction_results.py: 85%
143 statements
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-08 18:54 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-08 18:54 +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/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 .types import NetName, LayerName, CellName
30from ..log import error
32import klayout_pex_protobuf.kpex.r.r_network_pb2 as r_network_pb2
33import klayout_pex_protobuf.kpex.result.pex_result_pb2 as pex_result_pb2
34import klayout_pex_protobuf.kpex.tech.process_parasitics_pb2 as process_parasitics_pb2
37@dataclass
38class NodeRegion:
39 layer_name: LayerName
40 net_name: NetName
41 cap_to_gnd: float
42 perimeter: float
43 area: float
46@dataclass(frozen=True)
47class SidewallKey:
48 layer: LayerName
49 net1: NetName
50 net2: NetName
53@dataclass
54class SidewallCap: # see Magic EdgeCap, extractInt.c L444
55 key: SidewallKey
56 cap_value: float # femto farad
57 distance: float # distance in µm
58 length: float # length in µm
59 tech_spec: process_parasitics_pb2.CapacitanceInfo.SidewallCapacitance
62@dataclass(frozen=True)
63class OverlapKey:
64 layer_top: LayerName
65 net_top: NetName
66 layer_bot: LayerName
67 net_bot: NetName
70@dataclass
71class OverlapCap:
72 key: OverlapKey
73 cap_value: float # femto farad
74 shielded_area: float # in µm^2
75 unshielded_area: float # in µm^2
76 tech_spec: process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance
79@dataclass(frozen=True)
80class SideOverlapKey:
81 layer_inside: LayerName
82 net_inside: NetName
83 layer_outside: LayerName
84 net_outside: NetName
86 def __repr__(self) -> str:
87 return f"{self.layer_inside}({self.net_inside})-"\
88 f"{self.layer_outside}({self.net_outside})"
90 def __post_init__(self):
91 if self.layer_inside is None:
92 raise ValueError("layer_inside cannot be None")
93 if self.net_inside is None:
94 raise ValueError("net_inside cannot be None")
95 if self.layer_outside is None:
96 raise ValueError("layer_outside cannot be None")
97 if self.net_outside is None:
98 raise ValueError("net_outside cannot be None")
101@dataclass
102class SideOverlapCap:
103 key: SideOverlapKey
104 cap_value: float # femto farad
106 def __str__(self) -> str:
107 return f"(Side Overlap): {self.key} = {round(self.cap_value, 6)}fF"
110@dataclass(frozen=True)
111class NetCoupleKey:
112 net1: NetName
113 net2: NetName
115 def __repr__(self) -> str:
116 return f"{self.net1}-{self.net2}"
118 def __lt__(self, other) -> bool:
119 if not isinstance(other, NetCoupleKey):
120 raise NotImplemented
121 return (self.net1.casefold(), self.net2.casefold()) < (other.net1.casefold(), other.net2.casefold())
123 def __post_init__(self):
124 if self.net1 is None:
125 raise ValueError("net1 cannot be None")
126 if self.net2 is None:
127 raise ValueError("net2 cannot be None")
129 # NOTE: we norm net names alphabetically
130 def normed(self) -> NetCoupleKey:
131 if self.net1 < self.net2:
132 return self
133 else:
134 return NetCoupleKey(self.net2, self.net1)
137@dataclass
138class ExtractionSummary:
139 capacitances: Dict[NetCoupleKey, float]
140 resistances: Dict[NetCoupleKey, float]
142 @classmethod
143 def merged(cls, summaries: List[ExtractionSummary]) -> ExtractionSummary:
144 merged_capacitances = defaultdict(float)
145 merged_resistances = defaultdict(float)
146 for s in summaries:
147 for couple_key, cap in s.capacitances.items():
148 merged_capacitances[couple_key.normed()] += cap
149 for couple_key, res in s.resistances.items():
150 merged_resistances[couple_key.normed()] += res
151 return ExtractionSummary(capacitances=merged_capacitances,
152 resistances=merged_resistances)
155@dataclass
156class CellExtractionResults:
157 cell_name: CellName
159 overlap_table: Dict[OverlapKey, List[OverlapCap]] = field(default_factory=lambda: defaultdict(list))
160 sidewall_table: Dict[SidewallKey, List[SidewallCap]] = field(default_factory=lambda: defaultdict(list))
161 sideoverlap_table: Dict[SideOverlapKey, List[SideOverlapCap]] = field(default_factory=lambda: defaultdict(list))
163 r_extraction_result: pex_result_pb2.RExtractionResult = field(default_factory=lambda: pex_result_pb2.RExtractionResult())
165 def add_overlap_cap(self, cap: OverlapCap):
166 self.overlap_table[cap.key].append(cap)
168 def add_sidewall_cap(self, cap: SidewallCap):
169 self.sidewall_table[cap.key].append(cap)
171 def add_sideoverlap_cap(self, cap: SideOverlapCap):
172 self.sideoverlap_table[cap.key].append(cap)
174 def summarize(self) -> ExtractionSummary:
175 normalized_overlap_table: Dict[NetCoupleKey, float] = defaultdict(float)
176 for key, entries in self.overlap_table.items():
177 normalized_key = NetCoupleKey(key.net_bot, key.net_top).normed()
178 normalized_overlap_table[normalized_key] += sum((e.cap_value for e in entries))
179 overlap_summary = ExtractionSummary(capacitances=normalized_overlap_table,
180 resistances={})
182 normalized_sidewall_table: Dict[NetCoupleKey, float] = defaultdict(float)
183 for key, entries in self.sidewall_table.items():
184 normalized_key = NetCoupleKey(key.net1, key.net2).normed()
185 normalized_sidewall_table[normalized_key] += sum((e.cap_value for e in entries))
186 sidewall_summary = ExtractionSummary(capacitances=normalized_sidewall_table,
187 resistances={})
189 normalized_sideoverlap_table: Dict[NetCoupleKey, float] = defaultdict(float)
190 for key, entries in self.sideoverlap_table.items():
191 normalized_key = NetCoupleKey(key.net_inside, key.net_outside).normed()
192 normalized_sideoverlap_table[normalized_key] += sum((e.cap_value for e in entries))
193 sideoverlap_summary = ExtractionSummary(capacitances=normalized_sideoverlap_table,
194 resistances={})
196 normalized_resistance_table: Dict[NetCoupleKey, float] = defaultdict(float)
198 for network in self.r_extraction_result.networks:
199 node_by_id: Dict[int, r_network_pb2.RNode] = {n.node_id: n for n in network.nodes}
200 for element in network.elements:
201 node_a = node_by_id[element.node_a.node_id]
202 node_b = node_by_id[element.node_b.node_id]
203 resistance = element.resistance
204 normalized_key = NetCoupleKey(node_a.net_name, node_b.net_name).normed()
205 normalized_resistance_table[normalized_key] += resistance
207 resistance_summary = ExtractionSummary(capacitances={},
208 resistances=normalized_resistance_table)
210 return ExtractionSummary.merged([
211 overlap_summary, sidewall_summary, sideoverlap_summary,
212 resistance_summary
213 ])
216@dataclass
217class ExtractionResults:
218 cell_extraction_results: Dict[CellName, CellExtractionResults] = field(default_factory=dict)
220 def summarize(self) -> ExtractionSummary:
221 subsummaries = [s.summarize() for s in self.cell_extraction_results.values()]
222 return ExtractionSummary.merged(subsummaries)