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

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# 

24 

25from __future__ import annotations 

26from collections import defaultdict 

27from dataclasses import dataclass, field 

28from typing import * 

29 

30from .types import NetName, LayerName, CellName 

31from ..log import error 

32 

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 

36 

37 

38@dataclass 

39class NodeRegion: 

40 layer_name: LayerName 

41 net_name: NetName 

42 cap_to_gnd: float 

43 perimeter: float 

44 area: float 

45 

46 

47@dataclass(frozen=True) 

48class SidewallKey: 

49 layer: LayerName 

50 net1: NetName 

51 net2: NetName 

52 

53 

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 

61 

62 

63@dataclass(frozen=True) 

64class OverlapKey: 

65 layer_top: LayerName 

66 net_top: NetName 

67 layer_bot: LayerName 

68 net_bot: NetName 

69 

70 

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 

78 

79 

80@dataclass(frozen=True) 

81class SideOverlapKey: 

82 layer_inside: LayerName 

83 net_inside: NetName 

84 layer_outside: LayerName 

85 net_outside: NetName 

86 

87 def __repr__(self) -> str: 

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

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

90 

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

100 

101 

102@dataclass 

103class SideOverlapCap: 

104 key: SideOverlapKey 

105 cap_value: float # femto farad 

106 

107 def __str__(self) -> str: 

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

109 

110 

111@dataclass(frozen=True) 

112class NetCoupleKey: 

113 net1: NetName 

114 net2: NetName 

115 

116 def __repr__(self) -> str: 

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

118 

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

123 

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

129 

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) 

136 

137 

138@dataclass 

139class ExtractionSummary: 

140 capacitances: Dict[NetCoupleKey, float] 

141 resistances: Dict[NetCoupleKey, float] 

142 

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) 

154 

155 

156@dataclass 

157class CellExtractionResults: 

158 cell_name: CellName 

159 

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

163 

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

165 

166 def add_overlap_cap(self, cap: OverlapCap): 

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

168 

169 def add_sidewall_cap(self, cap: SidewallCap): 

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

171 

172 def add_sideoverlap_cap(self, cap: SideOverlapCap): 

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

174 

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

182 

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

189 

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

196 

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

198 

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 

209 

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 

219 

220 resistance_summary = ExtractionSummary(capacitances={}, 

221 resistances=normalized_resistance_table) 

222 

223 return ExtractionSummary.merged([ 

224 overlap_summary, sidewall_summary, sideoverlap_summary, 

225 resistance_summary 

226 ]) 

227 

228 

229@dataclass 

230class ExtractionResults: 

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

232 

233 def summarize(self) -> ExtractionSummary: 

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

235 return ExtractionSummary.merged(subsummaries)