Coverage for klayout_pex/tech_info.py: 81%

233 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-31 20:14 +0000

1#! /usr/bin/env python3 

2# 

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

4# SPDX-FileCopyrightText: 2024-2025 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/iic-jku/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 

32from .log import ( 

33 warning 

34) 

35 

36import klayout_pex_protobuf.kpex.tech.tech_pb2 as tech_pb2 

37import klayout_pex_protobuf.kpex.tech.process_stack_pb2 as process_stack_pb2 

38import klayout_pex_protobuf.kpex.tech.process_parasitics_pb2 as process_parasitics_pb2 

39 

40class TechInfo: 

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

42 

43 LVSLayerName = str 

44 CanonicalLayerName = str 

45 GDSPair = Tuple[int, int] 

46 

47 @staticmethod 

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

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

50 contents = f.read() 

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

52 return tech 

53 

54 @classmethod 

55 def from_json(cls, 

56 jsonpb_path: str, 

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

58 tech = cls.parse_tech_def(jsonpb_path=jsonpb_path) 

59 return TechInfo(tech=tech, 

60 dielectric_filter=dielectric_filter) 

61 

62 def __init__(self, 

63 tech: tech_pb2.Technology, 

64 dielectric_filter: Optional[MultipleChoicePattern]): 

65 self.tech = tech 

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

67 

68 @cached_property 

69 def gds_pair_for_computed_layer_name(self) -> Dict[LVSLayerName, GDSPair]: 

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

71 for lyr in self.tech.lvs_computed_layers} 

72 

73 @cached_property 

74 def computed_layer_info_by_name(self) -> Dict[LVSLayerName, tech_pb2.ComputedLayerInfo]: 

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

76 

77 @cached_property 

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

79 return { 

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

81 for lyr in self.tech.lvs_computed_layers 

82 } 

83 

84 @cached_property 

85 def canonical_layer_name_by_gds_pair(self) -> Dict[GDSPair, CanonicalLayerName]: 

86 return { 

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

88 for lyr in self.tech.lvs_computed_layers 

89 } 

90 

91 @cached_property 

92 def layer_info_by_name(self) -> Dict[CanonicalLayerName, tech_pb2.LayerInfo]: 

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

94 

95 @cached_property 

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

97 return { 

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

99 for m in self.tech.pin_layer_mappings 

100 } 

101 

102 @cached_property 

103 def gds_pair_for_layer_name(self) -> Dict[CanonicalLayerName, GDSPair]: 

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

105 

106 @cached_property 

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

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

109 

110 @cached_property 

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

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

113 

114 @cached_property 

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

116 return { 

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

118 for lyr in self.tech.process_stack.layers 

119 } 

120 

121 @cached_property 

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

123 return list( 

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

125 self.tech.process_stack.layers) 

126 )[0] 

127 

128 @cached_property 

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

130 return list( 

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

132 self.tech.process_stack.layers) 

133 ) 

134 

135 @cached_property 

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

137 return self.process_metal_layers[0] 

138 

139 @cached_property 

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

141 return list( 

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

143 self.tech.process_stack.layers) 

144 )[0] 

145 

146 @cached_property 

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

148 return list( 

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

150 self.tech.process_stack.layers) 

151 ) 

152 

153 @cached_property 

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

155 layers = [] 

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

157 match pl.layer_type: 

158 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC | \ 

159 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC | \ 

160 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC: 

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

162 layers.append(pl) 

163 return layers 

164 

165 @cached_property 

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

167 diel_by_name = {} 

168 for pl in self.filtered_dielectric_layers: 

169 match pl.layer_type: 

170 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC: 

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

172 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC: 

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

174 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC: 

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

176 return diel_by_name 

177 

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

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

180 for lyr in self.filtered_dielectric_layers: 

181 match lyr.layer_type: 

182 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC: 

183 if lyr.sidewall_dielectric_layer.reference == layer_name: 

184 found_layers.append(lyr) 

185 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC: 

186 if lyr.conformal_dielectric_layer.reference == layer_name: 

187 found_layers.append(lyr) 

188 case _: 

189 continue 

190 

191 if len(found_layers) == 0: 

192 return None 

193 if len(found_layers) >= 2: 

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

195 return found_layers[0] 

196 

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

198 """ 

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

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

201 """ 

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

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

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

205 if lyr.name == layer_name: 

206 found_layer = lyr 

207 elif found_layer: 

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

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

210 return None, 0.0 

211 diel_lyr = lyr 

212 # search for next metal or end of stack 

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

214 return diel_lyr, lyr.metal_layer.z - found_layer.metal_layer.z 

215 return diel_lyr, 5.0 # air TODO 

216 

217 @cached_property 

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

219 d = {} 

220 for lyr in self.process_metal_layers: 

221 contact = lyr.metal_layer.contact_above 

222 via_gds_pair = self.gds_pair(contact) 

223 canonical_via_name = self.canonical_layer_name_by_gds_pair[via_gds_pair] 

224 d[lyr.name] = canonical_via_name 

225 return d 

226 

227 @cached_property 

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

229 d = {} 

230 LT = process_stack_pb2.ProcessStackInfo.LayerType 

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

232 match lyr.layer_type: 

233 case LT.LAYER_TYPE_NWELL: 

234 d[lyr.name] = lyr.nwell_layer.contact_above 

235 

236 case LT.LAYER_TYPE_DIFFUSION: # nsdm or psdm 

237 d[lyr.name] = lyr.diffusion_layer.contact_above 

238 return d 

239 

240 @cached_property 

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

242 d = {} 

243 LT = process_stack_pb2.ProcessStackInfo.LayerType 

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

245 match lyr.layer_type: 

246 case LT.LAYER_TYPE_NWELL: 

247 d[lyr.nwell_layer.contact_above.name] = lyr.nwell_layer.contact_above 

248 

249 case LT.LAYER_TYPE_DIFFUSION: # nsdm or psdm 

250 d[lyr.diffusion_layer.contact_above.name] = lyr.diffusion_layer.contact_above 

251 

252 case LT.LAYER_TYPE_METAL: 

253 d[lyr.metal_layer.contact_above.name] = lyr.metal_layer.contact_above 

254 return d 

255 

256 def gds_pair(self, layer_name) -> Optional[GDSPair]: 

257 gds_pair = self.gds_pair_for_computed_layer_name.get(layer_name, None) 

258 if not gds_pair: 

259 gds_pair = self.gds_pair_for_layer_name.get(layer_name, None) 

260 if not gds_pair: 

261 warning(f"Can't find GDS pair for layer {layer_name}") 

262 return None 

263 return gds_pair 

264 

265 @cached_property 

266 def bottom_and_top_layer_name_by_via_computed_layer_name(self) -> Dict[str, Tuple[str, str]]: 

267 # NOTE: vias under the same name can be used in multiple situations 

268 # e.g. in sky130A, via3 has two (bot, top) cases: {(met3, met4), (met3, cmim)}, 

269 # therefore the canonical name must not be used, 

270 # but really the LVS computed name, that is also used in the process stack 

271 # 

272 # the metal layers however are canonical! 

273 

274 d = {} 

275 for metal_layer in self.process_metal_layers: 

276 layer_name = metal_layer.name 

277 gds_pair = self.gds_pair(layer_name) 

278 

279 if metal_layer.metal_layer.HasField('contact_above'): 

280 contact = metal_layer.metal_layer.contact_above 

281 d[contact.name] = (contact.layer_below, contact.metal_above) 

282 

283 return d 

284 #-------------------------------- 

285 

286 @cached_property 

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

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

289 

290 @cached_property 

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

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

293 

294 @cached_property 

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

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

297 

298 @staticmethod 

299 def milliohm_to_ohm(milliohm: float) -> float: 

300 # NOTE: tech_pb2 has mΩ/µm^2 

301 # RExtractorTech.Conductor.resistance is in Ω/µm^2 

302 return milliohm / 1000.0 

303 

304 @staticmethod 

305 def milliohm_by_cnt_to_ohm_by_square_for_contact( 

306 contact: process_stack_pb2.ProcessStackInfo.Contact, 

307 contact_resistance: process_parasitics_pb2.ResistanceInfo.ContactResistance) -> float: 

308 # NOTE: ContactResistance ... mΩ/CNT 

309 # 

310 ohm_by_square = contact_resistance.resistance / 1000.0 * contact.width ** 2 

311 return ohm_by_square 

312 

313 @staticmethod 

314 def milliohm_by_cnt_to_ohm_by_square_for_via( 

315 contact: process_stack_pb2.ProcessStackInfo.Contact, 

316 via_resistance: process_parasitics_pb2.ResistanceInfo.ViaResistance) -> float: 

317 ohm_by_square = via_resistance.resistance / 1000.0 * contact.width ** 2 

318 return ohm_by_square 

319 

320 #-------------------------------- 

321 

322 @cached_property 

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

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

325 

326 @cached_property 

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

328 """ 

329 usage: dict[top_layer_name][bottom_layer_name] 

330 """ 

331 

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

333 -> process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance: 

334 oc = process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance() 

335 oc.top_layer_name = sc.layer_name 

336 oc.bottom_layer_name = self.internal_substrate_layer_name 

337 oc.capacitance = sc.area_capacitance 

338 return oc 

339 

340 d = { 

341 ln: { 

342 self.internal_substrate_layer_name: convert_substrate_to_overlap_cap(sc) 

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

344 } 

345 

346 d2 = { 

347 oc.top_layer_name: { 

348 oc_bot.bottom_layer_name: oc_bot 

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

350 } 

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

352 } 

353 

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

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

356 if k1 not in d: 

357 d[k1] = {k2: v} 

358 else: 

359 d[k1][k2] = v 

360 return d 

361 

362 @cached_property 

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

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

365 

366 @property 

367 def internal_substrate_layer_name(self) -> str: 

368 return 'VSUBS' 

369 

370 @cached_property 

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

372 """ 

373 usage: dict[in_layer_name][out_layer_name] 

374 """ 

375 

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

377 -> process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance: 

378 soc = process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance() 

379 soc.in_layer_name = sc.layer_name 

380 soc.out_layer_name = self.internal_substrate_layer_name 

381 soc.capacitance = sc.perimeter_capacitance 

382 return soc 

383 

384 d = { 

385 ln: { 

386 self.internal_substrate_layer_name: convert_substrate_to_side_overlap_cap(sc) 

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

388 } 

389 

390 d2 = { 

391 oc.in_layer_name: { 

392 oc_bot.out_layer_name: oc_bot 

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

394 } 

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

396 } 

397 

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

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

400 d[k1][k2] = v 

401 

402 return d 

403