Coverage for klayout_pex/tech_info.py: 96%

321 statements  

« prev     ^ index     » next       coverage.py v7.17.2, created at 2026-11-06 13: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 collections import Counter 

29from fnmatch import fnmatchcase 

30from functools import cached_property 

31import google.protobuf.json_format 

32 

33from .device_models import DeviceModels 

34from .types import CanonicalLayerName, GDSPair, LayerName, LVSLayerName 

35from .util.multiple_choice import MultipleChoicePattern 

36from .log import ( 

37 warning 

38) 

39 

40import klayout_pex_protobuf.kpex.tech.tech_pb2 as tech_pb2 

41import klayout_pex_protobuf.kpex.tech.process_stack_pb2 as process_stack_pb2 

42import klayout_pex_protobuf.kpex.tech.process_parasitics_pb2 as process_parasitics_pb2 

43 

44class TechDefError(Exception): 

45 """A defect in the technology definition itself, not in a layout.""" 

46 

47 

48class TechInfo: 

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

50 

51 @staticmethod 

52 def duplicate_names(tech: tech_pb2.Technology) -> List[str]: 

53 """ 

54 Report every name one of the technology's namespaces declares twice. 

55 

56 Each of them is looked up by name, and the process stack's is also the 

57 PEX25D profile namespace, so a repeat adds nothing: it replaces or 

58 drops whatever it collides with. sky130A declaring 'capild' once per 

59 MiM-cap variant cost the second film, and left the dielectric above it 

60 wrapping the metal three levels down. 

61 """ 

62 namespaces: Dict[str, List[Tuple[str, str]]] = { 

63 'process stack': [], 

64 'layer': [], 

65 'LVS computed layer': [], 

66 'device model mapping': [], 

67 } 

68 

69 for lyr in tech.process_stack.layers: 

70 namespaces['process stack'].append((lyr.name, 'layer')) 

71 parameters = lyr.WhichOneof('parameters') 

72 # Only some layer kinds can carry a contact, and an unset one has 

73 # no name, so the name is the test rather than the layer type. 

74 contact = getattr(getattr(lyr, parameters), 'contact_above', None) \ 

75 if parameters else None 

76 if contact is not None and contact.name: 

77 namespaces['process stack'].append((contact.name, 'contact')) 

78 

79 namespaces['layer'] += [(lyr.name, 'layer') for lyr in tech.layers] 

80 namespaces['LVS computed layer'] += [(lyr.layer_info.name, 'layer') 

81 for lyr in tech.lvs_computed_layers] 

82 namespaces['device model mapping'] += [(dm.lvs_device_class_name, 'LVS device class') 

83 for dm in tech.device_models.device_model_mappings] 

84 

85 problems: List[str] = [] 

86 for namespace, declarations in namespaces.items(): 

87 counts = Counter(name for name, _ in declarations) 

88 for name, count in sorted(counts.items()): 

89 if count == 1: 

90 continue 

91 kinds = sorted({kind for n, kind in declarations if n == name}) 

92 problems.append( 

93 f"the {namespace} namespace declares '{name}' {count} times " 

94 f"(as {', '.join(kinds)}), so all but the first declaration " 

95 f"are dropped") 

96 return problems 

97 

98 @staticmethod 

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

100 with open(jsonpb_path, 'r', encoding='utf-8') as f: 

101 contents = f.read() 

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

103 

104 # Checked here rather than where a name is used: by then the collision 

105 # has already happened, and what it cost is no longer visible. 

106 problems = TechInfo.duplicate_names(tech) 

107 if problems: 

108 raise TechDefError( 

109 f"Names have to be unique, but in {jsonpb_path}" 

110 + ''.join(f"\n - {p}" for p in problems)) 

111 # NOTE: so are the layers of the process stack, which kpex looks up after reading, where it can't report 

112 problems = TechInfo.process_layer_problems(tech) 

113 if problems: 

114 raise TechDefError( 

115 f"LVS computed layers have to name the layers of the process stack on their GDS pairs, " 

116 f"but in {jsonpb_path}" + ''.join(f"\n - {p}" for p in problems)) 

117 return tech 

118 

119 @staticmethod 

120 def process_layer_problems(tech: tech_pb2.Technology) -> List[str]: 

121 """ 

122 Report every LVS computed layer that names no layer of the process stack on its GDS pair, 

123 where the stack has one, or names one that isn't (see lvs_layer_names_by_process_layer_name) 

124 """ 

125 return TechInfo(tech, dielectric_filter=None)._process_layers_of_lvs_layers[1] 

126 

127 @classmethod 

128 def from_json(cls, 

129 jsonpb_path: str, 

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

131 tech = cls.parse_tech_def(jsonpb_path=jsonpb_path) 

132 return TechInfo(tech=tech, 

133 dielectric_filter=dielectric_filter) 

134 

135 def __init__(self, 

136 tech: tech_pb2.Technology, 

137 dielectric_filter: Optional[MultipleChoicePattern]): 

138 self.tech = tech 

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

140 

141 @cached_property 

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

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

144 for lyr in self.tech.lvs_computed_layers} 

145 

146 @cached_property 

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

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

149 

150 @cached_property 

151 def device_models(self) -> DeviceModels: 

152 return DeviceModels(self.tech.device_models) 

153 

154 def device_gds_pairs(self, device_class_name: str) -> Optional[Set[GDSPair]]: 

155 """ 

156 :return: the layers of the layout that LVS makes the devices of the class from (see DeviceLayers), 

157 None if the tech info doesn't tell 

158 """ 

159 for device_layers in self.tech.device_layers: 

160 if any(fnmatchcase(device_class_name, pattern) for pattern in device_layers.lvs_device_class_patterns): 

161 return {(gds_pair.layer, gds_pair.datatype) for gds_pair in device_layers.gds_pairs} 

162 return None 

163 

164 @cached_property 

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

166 return { 

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

168 for lyr in self.tech.lvs_computed_layers 

169 } 

170 

171 @cached_property 

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

173 return { 

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

175 for lyr in self.tech.lvs_computed_layers 

176 } 

177 

178 @cached_property 

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

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

181 

182 @cached_property 

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

184 return { 

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

186 for m in self.tech.pin_layer_mappings 

187 } 

188 

189 @cached_property 

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

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

192 

193 @cached_property 

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

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

196 

197 @cached_property 

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

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

200 

201 @cached_property 

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

203 return { 

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

205 for lyr in self.tech.process_stack.layers 

206 } 

207 

208 @cached_property 

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

210 return list( 

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

212 self.tech.process_stack.layers) 

213 )[0] 

214 

215 @cached_property 

216 def process_conductor_gds_pairs(self) -> List[GDSPair]: 

217 """ 

218 The GDS pairs of the conductors of the process stack (its diffusion and metal layers), from the bottom up 

219 """ 

220 gds_pairs: List[GDSPair] = [] 

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

222 if lyr.layer_type not in (process_stack_pb2.ProcessStackInfo.LAYER_TYPE_DIFFUSION, 

223 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL): 

224 continue 

225 gds_pair = self.gds_pair_for_computed_layer_name.get(lyr.name, None) or \ 

226 self.gds_pair_for_layer_name.get(lyr.name, None) 

227 if gds_pair is not None and gds_pair not in gds_pairs: 

228 gds_pairs.append(gds_pair) 

229 return gds_pairs 

230 

231 @cached_property 

232 def lvs_layer_names_by_process_layer_name(self) -> Dict[LayerName, List[LVSLayerName]]: 

233 """ 

234 The LVS layers whose shapes each layer of the process stack (a conductor or a contact) has: 

235 those that name it (ComputedLayerInfo.process_layer_name), on its GDS pair 

236 

237 :raises TechDefError: if an LVS layer names no layer of the stack on its GDS pair, where the stack has one, 

238 or names one that isn't (see process_layer_problems) 

239 

240 NOTE: layers of the stack can share a GDS pair, e.g. sky130A met3_ncap and met3_cap, 

241 the parts of met3 without and with a MIM cap above, which have different dielectrics, 

242 so each of them has the shapes of its own LVS layer only 

243 (and met3_ncap also those of met3_vpp, the met3 of the MOM caps) 

244 """ 

245 lvs_layer_names_by_process_layer_name, problems = self._process_layers_of_lvs_layers 

246 if problems: 

247 raise TechDefError("LVS computed layers have to name the layers of the process stack on their GDS pairs:" 

248 + ''.join(f"\n - {p}" for p in problems)) 

249 return lvs_layer_names_by_process_layer_name 

250 

251 @cached_property 

252 def _process_layers_of_lvs_layers(self) -> Tuple[Dict[LayerName, List[LVSLayerName]], List[str]]: 

253 """ 

254 :return: lvs_layer_names_by_process_layer_name, and the problems (see process_layer_problems) 

255 """ 

256 stack_layer_names_by_gds_pair: Dict[GDSPair, List[LayerName]] = {} 

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

258 match lyr.layer_type: 

259 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_DIFFUSION: 

260 layer = lyr.diffusion_layer 

261 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL: 

262 layer = lyr.metal_layer 

263 case _: 

264 continue 

265 names = [lyr.name] 

266 if layer.HasField('contact_above'): 

267 names.append(layer.contact_above.name) 

268 for name in names: 

269 gds_pair = self.gds_pair_for_computed_layer_name.get(name, None) or \ 

270 self.gds_pair_for_layer_name.get(name, None) 

271 if gds_pair is not None: 

272 stack_layer_names_by_gds_pair.setdefault(gds_pair, []).append(name) 

273 

274 lvs_layer_names_by_process_layer_name: Dict[LayerName, List[LVSLayerName]] = {} 

275 problems: List[str] = [] 

276 for lvs_layer_name, gds_pair in self.gds_pair_for_computed_layer_name.items(): 

277 stack_layer_names = stack_layer_names_by_gds_pair.get(gds_pair, []) 

278 process_layer_name = self.computed_layer_info_by_name[lvs_layer_name].process_layer_name 

279 if not process_layer_name: 

280 if stack_layer_names: 

281 problems.append(f"the LVS computed layer {lvs_layer_name} names no layer of the process stack, " 

282 f"but the layers of the stack on its GDS pair {gds_pair} are: " 

283 f"{', '.join(stack_layer_names)}") 

284 continue 

285 if process_layer_name not in stack_layer_names: 

286 problems.append(f"the LVS computed layer {lvs_layer_name} names the layer {process_layer_name} " 

287 f"of the process stack, but the layers of the stack on its GDS pair {gds_pair} " 

288 f"are: {', '.join(stack_layer_names) or 'none'}") 

289 continue 

290 lvs_layer_names_by_process_layer_name.setdefault(process_layer_name, []).append(lvs_layer_name) 

291 return lvs_layer_names_by_process_layer_name, problems 

292 

293 @cached_property 

294 def process_layer_name_by_lvs_layer_name(self) -> Dict[LVSLayerName, LayerName]: 

295 """ 

296 The layer of the process stack (a conductor or a contact) of each LVS layer on one of its GDS pairs, 

297 see lvs_layer_names_by_process_layer_name 

298 """ 

299 return {lvs_layer_name: process_layer_name 

300 for process_layer_name, lvs_layer_names in self.lvs_layer_names_by_process_layer_name.items() 

301 for lvs_layer_name in lvs_layer_names} 

302 

303 @cached_property 

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

305 return list( 

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

307 self.tech.process_stack.layers) 

308 ) 

309 

310 @cached_property 

311 def process_diffusion_layer_names(self) -> List[CanonicalLayerName]: 

312 """ 

313 The canonical names of the diffusion layers, which the capacitances are looked up by 

314 (e.g. nsdm and psdm, of the LVS layers nsd and psd) 

315 """ 

316 gds_pairs = [self.gds_pair(lyr.name) for lyr in self.process_diffusion_layers] 

317 return [self.canonical_layer_name_by_gds_pair[gds_pair] for gds_pair in gds_pairs if gds_pair is not None] 

318 

319 @cached_property 

320 def substrate_well_layer_names(self) -> Set[CanonicalLayerName]: 

321 """ 

322 The canonical names of the wells the substrate is split into (see substrate.well_lvs_layer_names) 

323 """ 

324 return {self.canonical_layer_name_by_gds_pair[self.gds_pair_for_computed_layer_name[lvs_layer_name]] 

325 for lvs_layer_name in self.tech.substrate.well_lvs_layer_names 

326 if lvs_layer_name in self.gds_pair_for_computed_layer_name} 

327 

328 @cached_property 

329 def device_capacitance_layer_names(self) -> Set[CanonicalLayerName]: 

330 """ 

331 The layers between which the diffusion has the capacitances of the devices (see is_device_capacitance) 

332 """ 

333 gate_poly_layer_name = self.canonical_layer_name_by_gds_pair.get(self.gds_pair(self.gate_poly_layer.name), None) 

334 return {self.internal_substrate_layer_name, gate_poly_layer_name, *self.process_diffusion_layer_names} 

335 

336 def is_device_capacitance(self, layer_name1: CanonicalLayerName, layer_name2: CanonicalLayerName) -> bool: 

337 """ 

338 Whether the capacitance between the layers is the one of a device, which its model has, 

339 rather than a parasitic one: between the diffusion and the substrate or another diffusion (the junctions, 

340 e.g. by the AS, AD, PS and PD of a transistor), or the gate poly (the overlap of the gate) 

341 """ 

342 return (layer_name1 in self.process_diffusion_layer_names or layer_name2 in self.process_diffusion_layer_names) \ 

343 and layer_name1 in self.device_capacitance_layer_names \ 

344 and layer_name2 in self.device_capacitance_layer_names 

345 

346 @cached_property 

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

348 return self.process_metal_layers[0] 

349 

350 @cached_property 

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

352 return list( 

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

354 self.tech.process_stack.layers) 

355 )[0] 

356 

357 @cached_property 

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

359 return list( 

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

361 self.tech.process_stack.layers) 

362 ) 

363 

364 @cached_property 

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

366 layers = [] 

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

368 match pl.layer_type: 

369 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC | \ 

370 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC: 

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

372 layers.append(pl) 

373 return layers 

374 

375 @cached_property 

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

377 diel_by_name = {} 

378 for pl in self.filtered_dielectric_layers: 

379 match pl.layer_type: 

380 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC: 

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

382 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC: 

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

384 return diel_by_name 

385 

386 def conformal_dielectric_wrapping(self, layer_name: str) \ 

387 -> Optional[process_stack_pb2.ProcessStackInfo.LayerInfo]: 

388 """ 

389 The conformal dielectric anchored on ``layer_name``, if any. 

390 

391 Films form a chain: a metal is wrapped by a film, which may itself be 

392 wrapped by the next one. Calling this repeatedly walks that chain. 

393 """ 

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

395 for lyr in self.filtered_dielectric_layers: 

396 match lyr.layer_type: 

397 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC: 

398 if lyr.conformal_dielectric_layer.reference == layer_name: 

399 found_layers.append(lyr) 

400 case _: 

401 continue 

402 

403 if len(found_layers) == 0: 

404 return None 

405 if len(found_layers) >= 2: 

406 raise Exception(f"found multiple conformal dielectric layers wrapping {layer_name}") 

407 return found_layers[0] 

408 

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

410 """ 

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

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

413 """ 

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

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

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

417 if lyr.name == layer_name: 

418 found_layer = lyr 

419 elif found_layer: 

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

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

422 return None, 0.0 

423 diel_lyr = lyr 

424 # search for next metal or end of stack 

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

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

427 return diel_lyr, 5.0 # air TODO 

428 

429 @cached_property 

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

431 d = {} 

432 for lyr in self.process_metal_layers: 

433 contact = lyr.metal_layer.contact_above 

434 via_gds_pair = self.gds_pair(contact) 

435 canonical_via_name = self.canonical_layer_name_by_gds_pair[via_gds_pair] 

436 d[lyr.name] = canonical_via_name 

437 return d 

438 

439 @cached_property 

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

441 d = {} 

442 LT = process_stack_pb2.ProcessStackInfo.LayerType 

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

444 match lyr.layer_type: 

445 case LT.LAYER_TYPE_NWELL: 

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

447 

448 case LT.LAYER_TYPE_DIFFUSION: # nsdm or psdm 

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

450 

451 case LT.LAYER_TYPE_METAL: 

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

453 return d 

454 

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

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

457 if not gds_pair: 

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

459 if not gds_pair: 

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

461 return None 

462 return gds_pair 

463 

464 @cached_property 

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

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

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

468 # therefore the canonical name must not be used, 

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

470 # 

471 # the metal layers however are canonical! 

472 

473 d = {} 

474 for metal_layer in self.process_metal_layers: 

475 layer_name = metal_layer.name 

476 gds_pair = self.gds_pair(layer_name) 

477 

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

479 contact = metal_layer.metal_layer.contact_above 

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

481 

482 return d 

483 #-------------------------------- 

484 

485 @cached_property 

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

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

488 

489 @cached_property 

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

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

492 

493 @cached_property 

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

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

496 

497 @staticmethod 

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

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

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

501 return milliohm / 1000.0 

502 

503 @staticmethod 

504 def milliohm_by_cnt_to_ohm_by_square_for_contact( 

505 contact: process_stack_pb2.ProcessStackInfo.Contact, 

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

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

508 # 

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

510 return ohm_by_square 

511 

512 @staticmethod 

513 def milliohm_by_cnt_to_ohm_by_square_for_via( 

514 contact: process_stack_pb2.ProcessStackInfo.Contact, 

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

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

517 return ohm_by_square 

518 

519 #-------------------------------- 

520 

521 @cached_property 

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

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

524 

525 @cached_property 

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

527 """ 

528 usage: dict[top_layer_name][bottom_layer_name] 

529 """ 

530 

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

532 -> process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance: 

533 oc = process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance() 

534 oc.top_layer_name = sc.layer_name 

535 oc.bottom_layer_name = self.internal_substrate_layer_name 

536 oc.capacitance = sc.area_capacitance 

537 return oc 

538 

539 d = { 

540 ln: { 

541 self.internal_substrate_layer_name: convert_substrate_to_overlap_cap(sc) 

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

543 } 

544 

545 d2 = { 

546 oc.top_layer_name: { 

547 oc_bot.bottom_layer_name: oc_bot 

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

549 } 

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

551 } 

552 

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

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

555 if k1 not in d: 

556 d[k1] = {k2: v} 

557 else: 

558 d[k1][k2] = v 

559 return d 

560 

561 @cached_property 

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

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

564 

565 @property 

566 def internal_substrate_layer_name(self) -> str: 

567 return 'VSUBS' 

568 

569 @cached_property 

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

571 """ 

572 usage: dict[in_layer_name][out_layer_name] 

573 """ 

574 

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

576 -> process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance: 

577 soc = process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance() 

578 soc.in_layer_name = sc.layer_name 

579 soc.out_layer_name = self.internal_substrate_layer_name 

580 soc.capacitance = sc.perimeter_capacitance 

581 return soc 

582 

583 d = { 

584 ln: { 

585 self.internal_substrate_layer_name: convert_substrate_to_side_overlap_cap(sc) 

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

587 } 

588 

589 d2 = { 

590 oc.in_layer_name: { 

591 oc_bot.out_layer_name: oc_bot 

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

593 } 

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

595 } 

596 

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

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

599 d[k1][k2] = v 

600 

601 return d 

602