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

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 * 

28 

29from .types import NetName, LayerName, CellName 

30from ..log import error 

31 

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 

35 

36 

37@dataclass 

38class NodeRegion: 

39 layer_name: LayerName 

40 net_name: NetName 

41 cap_to_gnd: float 

42 perimeter: float 

43 area: float 

44 

45 

46@dataclass(frozen=True) 

47class SidewallKey: 

48 layer: LayerName 

49 net1: NetName 

50 net2: NetName 

51 

52 

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 

60 

61 

62@dataclass(frozen=True) 

63class OverlapKey: 

64 layer_top: LayerName 

65 net_top: NetName 

66 layer_bot: LayerName 

67 net_bot: NetName 

68 

69 

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 

77 

78 

79@dataclass(frozen=True) 

80class SideOverlapKey: 

81 layer_inside: LayerName 

82 net_inside: NetName 

83 layer_outside: LayerName 

84 net_outside: NetName 

85 

86 def __repr__(self) -> str: 

87 return f"{self.layer_inside}({self.net_inside})-"\ 

88 f"{self.layer_outside}({self.net_outside})" 

89 

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") 

99 

100 

101@dataclass 

102class SideOverlapCap: 

103 key: SideOverlapKey 

104 cap_value: float # femto farad 

105 

106 def __str__(self) -> str: 

107 return f"(Side Overlap): {self.key} = {round(self.cap_value, 6)}fF" 

108 

109 

110@dataclass(frozen=True) 

111class NetCoupleKey: 

112 net1: NetName 

113 net2: NetName 

114 

115 def __repr__(self) -> str: 

116 return f"{self.net1}-{self.net2}" 

117 

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()) 

122 

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") 

128 

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) 

135 

136 

137@dataclass 

138class ExtractionSummary: 

139 capacitances: Dict[NetCoupleKey, float] 

140 resistances: Dict[NetCoupleKey, float] 

141 

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) 

153 

154 

155@dataclass 

156class CellExtractionResults: 

157 cell_name: CellName 

158 

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)) 

162 

163 r_extraction_result: pex_result_pb2.RExtractionResult = field(default_factory=lambda: pex_result_pb2.RExtractionResult()) 

164 

165 def add_overlap_cap(self, cap: OverlapCap): 

166 self.overlap_table[cap.key].append(cap) 

167 

168 def add_sidewall_cap(self, cap: SidewallCap): 

169 self.sidewall_table[cap.key].append(cap) 

170 

171 def add_sideoverlap_cap(self, cap: SideOverlapCap): 

172 self.sideoverlap_table[cap.key].append(cap) 

173 

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={}) 

181 

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={}) 

188 

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={}) 

195 

196 normalized_resistance_table: Dict[NetCoupleKey, float] = defaultdict(float) 

197 

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 

206 

207 resistance_summary = ExtractionSummary(capacitances={}, 

208 resistances=normalized_resistance_table) 

209 

210 return ExtractionSummary.merged([ 

211 overlap_summary, sidewall_summary, sideoverlap_summary, 

212 resistance_summary 

213 ]) 

214 

215 

216@dataclass 

217class ExtractionResults: 

218 cell_extraction_results: Dict[CellName, CellExtractionResults] = field(default_factory=dict) 

219 

220 def summarize(self) -> ExtractionSummary: 

221 subsummaries = [s.summarize() for s in self.cell_extraction_results.values()] 

222 return ExtractionSummary.merged(subsummaries)