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
« 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#
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
33from .device_models import DeviceModels
34from .types import CanonicalLayerName, GDSPair, LayerName, LVSLayerName
35from .util.multiple_choice import MultipleChoicePattern
36from .log import (
37 warning
38)
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
44class TechDefError(Exception):
45 """A defect in the technology definition itself, not in a layout."""
48class TechInfo:
49 """Helper class for Protocol Buffer tech_pb2.Technology"""
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.
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 }
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'))
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]
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
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())
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
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]
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)
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')
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}
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}
150 @cached_property
151 def device_models(self) -> DeviceModels:
152 return DeviceModels(self.tech.device_models)
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
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 }
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 }
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}
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 }
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}
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}
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}
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 }
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]
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
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
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)
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
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)
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
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}
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 )
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]
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}
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}
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
346 @cached_property
347 def gate_poly_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
348 return self.process_metal_layers[0]
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]
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 )
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
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
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.
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
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]
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
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
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
448 case LT.LAYER_TYPE_DIFFUSION: # nsdm or psdm
449 d[lyr.diffusion_layer.contact_above.name] = lyr.diffusion_layer.contact_above
451 case LT.LAYER_TYPE_METAL:
452 d[lyr.metal_layer.contact_above.name] = lyr.metal_layer.contact_above
453 return d
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
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!
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)
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)
482 return d
483 #--------------------------------
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}
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}
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}
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
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
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
519 #--------------------------------
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}
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 """
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
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 }
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 }
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
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}
565 @property
566 def internal_substrate_layer_name(self) -> str:
567 return 'VSUBS'
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 """
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
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 }
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 }
597 for k1, ve in d2.items():
598 for k2, v in ve.items():
599 d[k1][k2] = v
601 return d