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

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 * 

28 

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 

33 

34 

35@dataclass 

36class NodeRegion: 

37 layer_name: LayerName 

38 net_name: NetName 

39 cap_to_gnd: float 

40 perimeter: float 

41 area: float 

42 

43 

44@dataclass(frozen=True) 

45class SidewallKey: 

46 layer: LayerName 

47 net1: NetName 

48 net2: NetName 

49 

50 

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 

58 

59 

60@dataclass(frozen=True) 

61class OverlapKey: 

62 layer_top: LayerName 

63 net_top: NetName 

64 layer_bot: LayerName 

65 net_bot: NetName 

66 

67 

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 

75 

76 

77@dataclass(frozen=True) 

78class SideOverlapKey: 

79 layer_inside: LayerName 

80 net_inside: NetName 

81 layer_outside: LayerName 

82 net_outside: NetName 

83 

84 def __repr__(self) -> str: 

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

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

87 

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

97 

98 

99@dataclass 

100class SideOverlapCap: 

101 key: SideOverlapKey 

102 cap_value: float # femto farad 

103 

104 def __str__(self) -> str: 

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

106 

107 

108@dataclass(frozen=True) 

109class NetCoupleKey: 

110 net1: NetName 

111 net2: NetName 

112 

113 def __repr__(self) -> str: 

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

115 

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

120 

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

126 

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) 

133 

134 

135@dataclass 

136class ExtractionSummary: 

137 capacitances: Dict[NetCoupleKey, float] 

138 resistances: Dict[NetCoupleKey, float] 

139 

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) 

151 

152 

153@dataclass 

154class CellExtractionResults: 

155 cell_name: CellName 

156 

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

160 

161 resistor_network: MultiLayerResistanceNetwork = \ 

162 field(default_factory=lambda: MultiLayerResistanceNetwork(resistor_networks_by_layer={}, via_resistors=[])) 

163 

164 def add_overlap_cap(self, cap: OverlapCap): 

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

166 

167 def add_sidewall_cap(self, cap: SidewallCap): 

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

169 

170 def add_sideoverlap_cap(self, cap: SideOverlapCap): 

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

172 

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

180 

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

187 

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

194 

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) 

225 

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 

230 

231 resistance_summary = ExtractionSummary(capacitances={}, 

232 resistances=normalized_resistance_table) 

233 

234 return ExtractionSummary.merged([ 

235 overlap_summary, sidewall_summary, sideoverlap_summary, 

236 resistance_summary 

237 ]) 

238 

239 

240@dataclass 

241class ExtractionResults: 

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

243 

244 def summarize(self) -> ExtractionSummary: 

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

246 return ExtractionSummary.merged(subsummaries)