Coverage for klayout_pex/tech_info.py: 92%

175 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-03-31 19:36 +0000

1#! /usr/bin/env python3 

2# 

3# -------------------------------------------------------------------------------- 

4# SPDX-FileCopyrightText: 2024 Martin Jan Köhler and Harald Pretl 

5# Johannes Kepler University, Institute for Integrated Circuits. 

6# 

7# This file is part of KPEX  

8# (see https://github.com/martinjankoehler/klayout-pex). 

9# 

10# This program is free software: you can redistribute it and/or modify 

11# it under the terms of the GNU General Public License as published by 

12# the Free Software Foundation, either version 3 of the License, or 

13# (at your option) any later version. 

14# 

15# This program is distributed in the hope that it will be useful, 

16# but WITHOUT ANY WARRANTY; without even the implied warranty of 

17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18# GNU General Public License for more details. 

19# 

20# You should have received a copy of the GNU General Public License 

21# along with this program. If not, see <http://www.gnu.org/licenses/>. 

22# SPDX-License-Identifier: GPL-3.0-or-later 

23# -------------------------------------------------------------------------------- 

24# 

25 

26from __future__ import annotations # allow class type hints within same class 

27from typing import * 

28from functools import cached_property 

29import google.protobuf.json_format 

30 

31from .util.multiple_choice import MultipleChoicePattern 

32import klayout_pex_protobuf.tech_pb2 as tech_pb2 

33import klayout_pex_protobuf.process_stack_pb2 as process_stack_pb2 

34import klayout_pex_protobuf.process_parasitics_pb2 as process_parasitics_pb2 

35 

36class TechInfo: 

37 """Helper class for Protocol Buffer tech_pb2.Technology""" 

38 

39 GDSPair = Tuple[int, int] 

40 

41 @staticmethod 

42 def parse_tech_def(jsonpb_path: str) -> tech_pb2.Technology: 

43 with open(jsonpb_path, 'r') as f: 

44 contents = f.read() 

45 tech = google.protobuf.json_format.Parse(contents, tech_pb2.Technology()) 

46 return tech 

47 

48 @classmethod 

49 def from_json(cls, 

50 jsonpb_path: str, 

51 dielectric_filter: Optional[MultipleChoicePattern]) -> TechInfo: 

52 tech = cls.parse_tech_def(jsonpb_path=jsonpb_path) 

53 return TechInfo(tech=tech, 

54 dielectric_filter=dielectric_filter) 

55 

56 def __init__(self, 

57 tech: tech_pb2.Technology, 

58 dielectric_filter: Optional[MultipleChoicePattern]): 

59 self.tech = tech 

60 self.dielectric_filter = dielectric_filter or MultipleChoicePattern(pattern='all') 

61 

62 @cached_property 

63 def gds_pair_for_computed_layer_name(self) -> Dict[str, GDSPair]: 

64 return {lyr.layer_info.name: (lyr.layer_info.drw_gds_pair.layer, lyr.layer_info.drw_gds_pair.datatype) 

65 for lyr in self.tech.lvs_computed_layers} 

66 

67 @cached_property 

68 def computed_layer_info_by_name(self) -> Dict[str, tech_pb2.ComputedLayerInfo]: 

69 return {lyr.layer_info.name: lyr for lyr in self.tech.lvs_computed_layers} 

70 

71 @cached_property 

72 def computed_layer_info_by_gds_pair(self) -> Dict[GDSPair, tech_pb2.ComputedLayerInfo]: 

73 return { 

74 (lyr.layer_info.drw_gds_pair.layer, lyr.layer_info.drw_gds_pair.datatype): lyr 

75 for lyr in self.tech.lvs_computed_layers 

76 } 

77 

78 @cached_property 

79 def canonical_layer_name_by_gds_pair(self) -> Dict[GDSPair, str]: 

80 return { 

81 (lyr.layer_info.drw_gds_pair.layer, lyr.layer_info.drw_gds_pair.datatype): lyr.original_layer_name 

82 for lyr in self.tech.lvs_computed_layers 

83 } 

84 

85 @cached_property 

86 def layer_info_by_name(self) -> Dict[str, tech_pb2.LayerInfo]: 

87 return {lyr.name: lyr for lyr in self.tech.layers} 

88 

89 @cached_property 

90 def pin_layer_mapping_for_drw_gds_pair(self) -> Dict[GDSPair, tech_pb2.PinLayerMapping]: 

91 return { 

92 (m.drw_gds_layer, m.drw_gds_datatype): (m.pin_gds_layer, m.pin_gds_datatype) 

93 for m in self.tech.pin_layer_mappings 

94 } 

95 

96 @cached_property 

97 def gds_pair_for_layer_name(self) -> Dict[str, GDSPair]: 

98 return {lyr.name: (lyr.drw_gds_pair.layer, lyr.drw_gds_pair.datatype) for lyr in self.tech.layers} 

99 

100 @cached_property 

101 def layer_info_by_gds_pair(self) -> Dict[GDSPair, tech_pb2.LayerInfo]: 

102 return {(lyr.drw_gds_pair.layer, lyr.drw_gds_pair.datatype): lyr for lyr in self.tech.layers} 

103 

104 @cached_property 

105 def process_stack_layer_by_name(self) -> Dict[str, process_stack_pb2.ProcessStackInfo.LayerInfo]: 

106 return {lyr.name: lyr for lyr in self.tech.process_stack.layers} 

107 

108 @cached_property 

109 def process_stack_layer_by_gds_pair(self) -> Dict[GDSPair, process_stack_pb2.ProcessStackInfo.LayerInfo]: 

110 return { 

111 (lyr.drw_gds_pair.layer, lyr.drw_gds_pair.datatype): self.process_stack_layer_by_name[lyr.name] 

112 for lyr in self.tech.process_stack.layers 

113 } 

114 

115 @cached_property 

116 def process_substrate_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo: 

117 return list( 

118 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SUBSTRATE, 

119 self.tech.process_stack.layers) 

120 )[0] 

121 

122 @cached_property 

123 def process_diffusion_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]: 

124 return list( 

125 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_DIFFUSION, 

126 self.tech.process_stack.layers) 

127 ) 

128 

129 @cached_property 

130 def gate_poly_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo: 

131 return self.process_metal_layers[0] 

132 

133 @cached_property 

134 def field_oxide_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo: 

135 return list( 

136 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_FIELD_OXIDE, 

137 self.tech.process_stack.layers) 

138 )[0] 

139 

140 @cached_property 

141 def process_metal_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]: 

142 return list( 

143 filter(lambda lyr: lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL, 

144 self.tech.process_stack.layers) 

145 ) 

146 

147 @cached_property 

148 def filtered_dielectric_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]: 

149 layers = [] 

150 for pl in self.tech.process_stack.layers: 

151 match pl.layer_type: 

152 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC | \ 

153 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC | \ 

154 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC: 

155 if self.dielectric_filter.is_included(pl.name): 

156 layers.append(pl) 

157 return layers 

158 

159 @cached_property 

160 def dielectric_by_name(self) -> Dict[str, float]: 

161 diel_by_name = {} 

162 for pl in self.filtered_dielectric_layers: 

163 match pl.layer_type: 

164 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC: 

165 diel_by_name[pl.name] = pl.simple_dielectric_layer.dielectric_k 

166 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC: 

167 diel_by_name[pl.name] = pl.conformal_dielectric_layer.dielectric_k 

168 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC: 

169 diel_by_name[pl.name] = pl.sidewall_dielectric_layer.dielectric_k 

170 return diel_by_name 

171 

172 def sidewall_dielectric_layer(self, layer_name: str) -> Optional[process_stack_pb2.ProcessStackInfo.LayerInfo]: 

173 found_layers: List[process_stack_pb2.ProcessStackInfo.LayerInfo] = [] 

174 for lyr in self.filtered_dielectric_layers: 

175 match lyr.layer_type: 

176 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC: 

177 if lyr.sidewall_dielectric_layer.reference == layer_name: 

178 found_layers.append(lyr) 

179 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC: 

180 if lyr.conformal_dielectric_layer.reference == layer_name: 

181 found_layers.append(lyr) 

182 case _: 

183 continue 

184 

185 if len(found_layers) == 0: 

186 return None 

187 if len(found_layers) >= 2: 

188 raise Exception(f"found multiple sidewall dielectric layers for {layer_name}") 

189 return found_layers[0] 

190 

191 def simple_dielectric_above_metal(self, layer_name: str) -> Tuple[Optional[process_stack_pb2.ProcessStackInfo.LayerInfo], float]: 

192 """ 

193 Returns a tuple of the dielectric layer and it's (maximum) height. 

194 Maximum would be the case where no metal and other dielectrics are present. 

195 """ 

196 found_layer: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None 

197 diel_lyr: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None 

198 for lyr in self.tech.process_stack.layers: 

199 if lyr.name == layer_name: 

200 found_layer = lyr 

201 elif found_layer: 

202 if not diel_lyr and lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC: 

203 if not self.dielectric_filter.is_included(lyr.name): 

204 return None, 0.0 

205 diel_lyr = lyr 

206 # search for next metal or end of stack 

207 if lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL: 

208 return diel_lyr, lyr.metal_layer.height - found_layer.metal_layer.height 

209 return diel_lyr, 5.0 # air TODO 

210 

211 @cached_property 

212 def contact_above_metal_layer_name(self) -> Dict[str, process_stack_pb2.ProcessStackInfo.Contact]: 

213 return {lyr.name: lyr.metal_layer.contact_above 

214 for lyr in self.process_metal_layers} 

215 

216 @cached_property 

217 def contact_by_contact_layer_name(self) -> Dict[str, process_stack_pb2.ProcessStackInfo.Contact]: 

218 return {lyr.metal_layer.contact_above.name: lyr.metal_layer.contact_above 

219 for lyr in self.process_metal_layers} 

220 

221 #-------------------------------- 

222 

223 @cached_property 

224 def layer_resistance_by_layer_name(self) -> Dict[str, process_parasitics_pb2.ResistanceInfo.LayerResistance]: 

225 return {r.layer_name: r for r in self.tech.process_parasitics.resistance.layers} 

226 

227 @cached_property 

228 def contact_resistance_by_layer_name(self) -> Dict[str, process_parasitics_pb2.ResistanceInfo.ContactResistance]: 

229 return {r.device_layer_name: r for r in self.tech.process_parasitics.resistance.contacts} 

230 

231 @cached_property 

232 def via_resistance_by_layer_name(self) -> Dict[str, process_parasitics_pb2.ResistanceInfo.ViaResistance]: 

233 return {r.via_name: r for r in self.tech.process_parasitics.resistance.vias} 

234 

235 #-------------------------------- 

236 

237 @cached_property 

238 def substrate_cap_by_layer_name(self) -> Dict[str, process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance]: 

239 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.substrates} 

240 

241 @cached_property 

242 def overlap_cap_by_layer_names(self) -> Dict[str, Dict[str, process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance]]: 

243 """ 

244 usage: dict[top_layer_name][bottom_layer_name] 

245 """ 

246 

247 def convert_substrate_to_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \ 

248 -> process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance: 

249 oc = process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance() 

250 oc.top_layer_name = sc.layer_name 

251 oc.bottom_layer_name = self.internal_substrate_layer_name 

252 oc.capacitance = sc.area_capacitance 

253 return oc 

254 

255 d = { 

256 ln: { 

257 self.internal_substrate_layer_name: convert_substrate_to_overlap_cap(sc) 

258 } for ln, sc in self.substrate_cap_by_layer_name.items() 

259 } 

260 

261 d2 = { 

262 oc.top_layer_name: { 

263 oc_bot.bottom_layer_name: oc_bot 

264 for oc_bot in self.tech.process_parasitics.capacitance.overlaps if oc_bot.top_layer_name == oc.top_layer_name 

265 } 

266 for oc in self.tech.process_parasitics.capacitance.overlaps 

267 } 

268 

269 for k1, ve in d2.items(): 

270 for k2, v in ve.items(): 

271 if k1 not in d: 

272 d[k1] = {k2: v} 

273 else: 

274 d[k1][k2] = v 

275 return d 

276 

277 @cached_property 

278 def sidewall_cap_by_layer_name(self) -> Dict[str, process_parasitics_pb2.CapacitanceInfo.SidewallCapacitance]: 

279 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.sidewalls} 

280 

281 @classmethod 

282 @property 

283 def internal_substrate_layer_name(cls) -> str: 

284 return 'VSUBS' 

285 

286 @cached_property 

287 def side_overlap_cap_by_layer_names(self) -> Dict[str, Dict[str, process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance]]: 

288 """ 

289 usage: dict[in_layer_name][out_layer_name] 

290 """ 

291 

292 def convert_substrate_to_side_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \ 

293 -> process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance: 

294 soc = process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance() 

295 soc.in_layer_name = sc.layer_name 

296 soc.out_layer_name = self.internal_substrate_layer_name 

297 soc.capacitance = sc.perimeter_capacitance 

298 return soc 

299 

300 d = { 

301 ln: { 

302 self.internal_substrate_layer_name: convert_substrate_to_side_overlap_cap(sc) 

303 } for ln, sc in self.substrate_cap_by_layer_name.items() 

304 } 

305 

306 d2 = { 

307 oc.in_layer_name: { 

308 oc_bot.out_layer_name: oc_bot 

309 for oc_bot in self.tech.process_parasitics.capacitance.sideoverlaps if oc_bot.in_layer_name == oc.in_layer_name 

310 } 

311 for oc in self.tech.process_parasitics.capacitance.sideoverlaps 

312 } 

313 

314 for k1, ve in d2.items(): 

315 for k2, v in ve.items(): 

316 d[k1][k2] = v 

317 

318 return d 

319