Coverage for klayout_pex/rcx25/extractor.py: 96%
297 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#
26import klayout.db as kdb
28from ..klayout.lvsdb_extractor import DEVICE_CAPACITOR_PLATE_PROPERTY, KLayoutExtractionContext
29from ..log import (
30 debug,
31 warning,
32 error,
33 info,
34 subproc,
35 rule
36)
37from ..tech_info import TechInfo
38from ..types import GDSPair, LVSLayerName
39from ..util.peak_memory import log_peak_memory
40from .extraction_results import *
41from .extraction_reporter import ExtractionReporter
42from .exclusion import BlockKind, CutLayers, ExcludedBlock, Exclusions, listed_names
43from .netlist_expander import RCX25NetlistExpander
44from .pex_mode import PEXMode
45from klayout_pex.rcx25.c.device_capacitor_plates import DeviceCapacitorPlates
46from klayout_pex.rcx25.c.overlap_extractor import OverlapExtractor
47from klayout_pex.rcx25.c.sidewall_and_fringe_extractor import SidewallAndFringeExtractor
48from klayout_pex.rcx25.r.r_extractor import RExtractor
50import klayout_pex_protobuf.kpex.geometry.shapes_pb2 as shapes_pb2
51import klayout_pex_protobuf.kpex.layout.location_pb2 as location_pb2
52import klayout_pex_protobuf.kpex.request.pex_request_pb2 as pex_request_pb2
53import klayout_pex_protobuf.kpex.result.pex_result_pb2 as pex_result_pb2
54import klayout_pex_protobuf.kpex.klayout.r_extractor_tech_pb2 as rex_tech_pb2
55import klayout_pex_protobuf.kpex.tech.tech_pb2 as tech_pb2
56from klayout_pex_protobuf.kpex.klayout.r_extractor_tech_pb2 import RExtractorTech as pb_RExtractorTech
59class CExtractionTechError(Exception):
60 """
61 The tech info can't model the capacitances of the layout
62 """
63 pass
66def is_on_tap(lvs_layer_name: LVSLayerName, tech_info: TechInfo) -> bool:
67 LP = tech_pb2.LayerInfo.Purpose
68 computed_layer_info = tech_info.computed_layer_info_by_name.get(lvs_layer_name, None)
69 return computed_layer_info is not None and \
70 computed_layer_info.layer_info.purpose in (LP.PURPOSE_NTAP, LP.PURPOSE_PTAP, LP.PURPOSE_NTAP_OR_PTAP)
73def is_on_well(lvs_layer_name: LVSLayerName, tech_info: TechInfo) -> bool:
74 LP = tech_pb2.LayerInfo.Purpose
75 computed_layer_info = tech_info.computed_layer_info_by_name.get(lvs_layer_name, None)
76 return computed_layer_info is not None and \
77 computed_layer_info.layer_info.purpose in (LP.PURPOSE_NWELL, LP.PURPOSE_PWELL, LP.PURPOSE_DNWELL)
80def devices_of_cut_cells(exclusions: Exclusions,
81 pex_context: KLayoutExtractionContext,
82 tech_info: TechInfo) -> Dict[int, int]:
83 """
84 :return: the devices within the cut cells by their IDs, with the indexes of their blocks: those that a cell draws,
85 by the layers that LVS makes them from (see TechInfo.device_gds_pairs), at their terminals, but those
86 on the substrate, and on a tap, which goes on beyond the cell, unless the device is a tap
87 (see CutCellInstance.draws_device)
89 NOTE: by geometry, as the LVS netlist is flat
90 """
91 cut_block_indexes = {index for index, block in enumerate(exclusions.blocks) if block.kind == BlockKind.CUT}
92 if not cut_block_indexes:
93 return {}
95 lvsdb = pex_context.lvsdb
96 substrate_lvs_layer_names = set(tech_info.tech.substrate.lvs_layer_names)
97 gds_pairs_by_class: Dict[str, Optional[Set[GDSPair]]] = {}
98 blocks: Dict[int, int] = {}
99 for device in pex_context.top_circuit.each_device():
100 class_name = device.device_class().name
101 if class_name not in gds_pairs_by_class:
102 gds_pairs_by_class[class_name] = tech_info.device_gds_pairs(class_name)
103 gds_pairs = gds_pairs_by_class[class_name]
104 if gds_pairs is None:
105 continue
107 terminals: List[kdb.Region] = []
108 on_taps: List[kdb.Region] = []
109 is_a_tap = True
110 bbox = kdb.Box()
111 for terminal_definition in device.device_class().terminal_definitions():
112 for reference in pex_context.terminal_refs[(device.id(), terminal_definition.id())]:
113 for index, shapes in pex_context.shapes_of_terminal(device, reference).items():
114 lvs_layer_name = lvsdb.layer_name(index)
115 if shapes.is_empty() or lvs_layer_name in substrate_lvs_layer_names:
116 continue
117 if is_on_tap(lvs_layer_name, tech_info):
118 on_taps.append(shapes)
119 continue
120 terminals.append(shapes)
121 bbox += shapes.bbox()
122 is_a_tap &= is_on_well(lvs_layer_name, tech_info)
123 if is_a_tap:
124 terminals += on_taps
125 for shapes in on_taps:
126 bbox += shapes.bbox()
128 block_index = next((index for index in exclusions.block_indexes_touching(bbox)
129 if index in cut_block_indexes
130 and exclusions.blocks[index].cell_instance.draws_device(gds_pairs, terminals)), None)
131 if block_index is not None:
132 blocks[device.id()] = block_index
134 unknown_classes = sorted(name for name, gds_pairs in gds_pairs_by_class.items() if gds_pairs is None)
135 if unknown_classes:
136 warning(f"The tech info doesn't tell what LVS makes these device classes from, so the netlist keeps "
137 f"their devices, also of the cut cells: {', '.join(unknown_classes)}")
138 return blocks
141def add_filled_diffusion_to_cut_cells(exclusions: Exclusions,
142 pex_context: KLayoutExtractionContext,
143 tech_info: TechInfo):
144 """
145 The cut cells stand for what LVS computes of their devices where the layout draws no diffusion,
146 e.g. the drain of an LDMOS, across the gap to its gate
147 """
148 diffusion_layer_names = set(tech_info.process_diffusion_layer_names)
149 diffusion_gds_pairs = CutLayers.of_tech(tech_info).diffusion_gds_pairs
150 for device in pex_context.top_circuit.each_device():
151 block_index = exclusions.removed_device_blocks.get(device.id(), None)
152 if block_index is None:
153 continue
154 instance = exclusions.blocks[block_index].cell_instance
155 for terminal_definition in device.device_class().terminal_definitions():
156 for reference in pex_context.terminal_refs[(device.id(), terminal_definition.id())]:
157 for index, shapes in pex_context.shapes_of_terminal(device, reference).items():
158 lvs_layer_name = pex_context.lvsdb.layer_name(index)
159 computed_layer_info = tech_info.computed_layer_info_by_name.get(lvs_layer_name, None)
160 layer_name = computed_layer_info.original_layer_name if computed_layer_info is not None else None
161 # NOTE: not the box of a ring (e.g. the collector of a Schottky diode), which is no gap of a device
162 if layer_name in diffusion_layer_names and not is_on_tap(lvs_layer_name, tech_info):
163 filled = shapes - instance.shapes_of_layout(diffusion_gds_pairs, shapes.bbox())
164 if not filled.is_empty():
165 exclusions.add_to_cut_cell(block_index, layer_name, filled)
168def prepare_exclusions(exclusions: Exclusions,
169 pex_context: KLayoutExtractionContext,
170 tech_info: TechInfo):
171 """
172 Finds the nets within the exclusion blocks that nothing connects to the netlist,
173 and warns of what the extraction leaves out with the markers beyond what they stand for
174 """
175 if not exclusions.blocks:
176 return
178 exclusions.removed_device_blocks = devices_of_cut_cells(exclusions, pex_context, tech_info)
179 if exclusions.removed_device_blocks:
180 info(f"The netlist leaves out the {len(exclusions.removed_device_blocks)} devices of the cut cells")
181 add_filled_diffusion_to_cut_cells(exclusions, pex_context, tech_info)
183 K = tech_pb2.ComputedLayerInfo.Kind
184 layers_within: Dict[NetName, Set[LayerName]] = defaultdict(set)
185 layers_within_markers: Dict[NetName, Set[LayerName]] = defaultdict(set)
186 nets_outside: Set[NetName] = set()
187 nets_outside_markers: Set[NetName] = set()
188 # NOTE: not the wells and the substrate below the markers, which are in the models of what they stand for
189 for gds_pair in tech_info.process_conductor_gds_pairs:
190 extracted_layer = pex_context.extracted_layers.get(gds_pair, None)
191 if extracted_layer is None:
192 continue
193 layer_name = tech_info.canonical_layer_name_by_gds_pair[gds_pair]
194 mask = exclusions.mask(layer_name)
195 marker_mask = exclusions.mask(layer_name, kind=BlockKind.MARKER)
196 for source_layer in extracted_layer.source_layers:
197 kind = tech_info.computed_layer_info_by_name[source_layer.lvs_layer_name].kind
198 if kind in (K.KIND_PIN, K.KIND_LABEL):
199 continue
200 shapes_by_net = pex_context.shapes_by_net_name_by_lvs_layer[source_layer.lvs_layer_name]
201 for net_name, shapes in shapes_by_net.items():
202 for region, within, outside in ((mask, layers_within, nets_outside),
203 (marker_mask, layers_within_markers, nets_outside_markers)):
204 box = shapes.bbox()
205 if not box.touches(region.bbox()):
206 outside.add(net_name)
207 continue
208 near = exclusions.regions.near(region, box)
209 if not (shapes & near).is_empty():
210 within[net_name].add(layer_name)
211 if not (shapes - near).is_empty():
212 outside.add(net_name)
214 circuit = pex_context.top_circuit
215 blackbox_devices = pex_context.blackbox_devices
216 device_models = tech_info.device_models
218 def is_whiteboxed(device: kdb.Device) -> bool:
219 return not blackbox_devices and RCX25NetlistExpander.is_whiteboxed(device.device_class(), device_models)
221 def is_removed(device: kdb.Device) -> bool:
222 return device.id() in exclusions.removed_device_blocks or is_whiteboxed(device)
224 def is_connected(net_name: NetName) -> bool:
225 net = circuit.net_by_name(net_name)
226 return net is None or net.pin_count() + net.subcircuit_pin_count() > 0 or \
227 any(not is_removed(terminal.device()) for terminal in net.each_terminal())
229 exclusions.floating_net_names = {net_name for net_name in layers_within
230 if net_name not in nets_outside and not is_connected(net_name)}
232 nets_across = sorted(net_name for net_name in layers_within_markers if net_name in nets_outside_markers)
233 if nets_across:
234 listed_count = 20
235 warning("These nets are partly within exclusion markers, so the extraction leaves out their parasitics "
236 "there, for the model of what the markers stand for (a wire across a marker loses them too):\n" +
237 '\n'.join(f" - {net_name}: {', '.join(sorted(layers_within_markers[net_name]))}"
238 for net_name in nets_across[:listed_count]) +
239 (f"\n - and {len(nets_across) - listed_count} more" if len(nets_across) > listed_count else ''))
240 if exclusions.floating_net_names:
241 info(f"Nothing connects these nets within exclusion markers to the netlist, "
242 f"so their capacitances to the rest of the layout go to the substrate: "
243 f"{listed_names(sorted(exclusions.floating_net_names))}")
245 substrate_lvs_layer_names = set(tech_info.tech.substrate.lvs_layer_names)
247 def is_plate_within(block: ExcludedBlock, lvs_layer_name: LVSLayerName, shapes: kdb.Region) -> bool:
248 computed_layer_info = tech_info.computed_layer_info_by_name.get(lvs_layer_name, None)
249 region = block.region if computed_layer_info is None \
250 else block.region_of(computed_layer_info.original_layer_name)
251 return region is not None and not (shapes & exclusions.regions.near(region, shapes.bbox())).is_empty()
253 def has_plates_within_a_block(device: kdb.Device) -> bool:
254 """
255 NOTE: all of them, as what a capacitance is left out by has both of its plates within one block
256 """
257 plates: List[List[Tuple[LVSLayerName, kdb.Region]]] = []
258 box = kdb.Box()
259 for terminal in device.device_class().terminal_definitions():
260 plate: List[Tuple[LVSLayerName, kdb.Region]] = []
261 for reference in pex_context.terminal_refs[(device.id(), terminal.id())]:
262 for index, shapes in pex_context.shapes_of_terminal(device, reference).items():
263 lvs_layer_name = pex_context.lvsdb.layer_name(index)
264 if not shapes.is_empty() and lvs_layer_name not in substrate_lvs_layer_names:
265 plate.append((lvs_layer_name, shapes))
266 box += shapes.bbox()
267 if plate:
268 plates.append(plate)
269 return any(block.capacitance_pair is None and
270 all(any(is_plate_within(block, *part) for part in plate) for plate in plates)
271 for block in exclusions.blocks_touching(box))
273 lost_devices = [d.expanded_name() for d in circuit.each_device()
274 if is_whiteboxed(d) and d.id() not in exclusions.removed_device_blocks
275 and has_plates_within_a_block(d)]
276 if lost_devices:
277 warning(f"The netlist loses these devices within exclusion markers, as white-box mode (--blackbox n) "
278 f"removes them, and the capacitances of their plates are left out, use --blackbox y to keep them: "
279 f"{listed_names(sorted(lost_devices))}")
282def warn_of_ports_without_nodes(exclusions: Exclusions, netlist_description: str):
283 """
284 :param netlist_description: of a netlist with one node per net
285 """
286 if any(block.ports for block in exclusions.blocks):
287 warning(f"{netlist_description} has one node per net, so the netlist has no ports where nets enter "
288 f"what is left out, use --mode R or RC of the 2.5D engine to connect a model")
291class RCX25Extractor:
292 def __init__(self,
293 pex_context: KLayoutExtractionContext,
294 pex_mode: PEXMode,
295 scale_ratio_to_fit_halo: bool,
296 delaunay_amax: float,
297 delaunay_b: float,
298 tech_info: TechInfo,
299 report_path: str,
300 report_caps: bool,
301 exclusions: Optional[Exclusions] = None):
302 """
303 :param report_caps: the report has each capacitance contribution with its shapes (see ExtractionReporter)
304 :param exclusions: what a model has the parasitics of (see exclusion.py), after prepare_exclusions
305 """
306 self.pex_context = pex_context
307 self.pex_mode = pex_mode
308 self.scale_ratio_to_fit_halo = scale_ratio_to_fit_halo
309 self.delaunay_amax = delaunay_amax
310 self.delaunay_b = delaunay_b
311 self.tech_info = tech_info
312 self.report_path = report_path
313 self.report_caps = report_caps
314 self.exclusions = exclusions
316 if "PolygonWithProperties" not in kdb.__all__:
317 raise Exception("KLayout version does not support properties (needs 0.30 at least)")
319 # TODO: remove this function by inlining
320 def gds_pair(self, layer_name) -> Optional[GDSPair]:
321 return self.tech_info.gds_pair(layer_name)
323 def shapes_of_layer(self, layer_name: str, mark_device_capacitor_plates: bool = False) -> Optional[kdb.Region]:
324 gds_pair = self.gds_pair(layer_name=layer_name)
325 if not gds_pair:
326 return None
328 shapes = self.pex_context.shapes_of_layer(gds_pair=gds_pair,
329 mark_device_capacitor_plates=mark_device_capacitor_plates)
330 if not shapes:
331 debug(f"Nothing extracted for layer {layer_name}")
333 return shapes
335 def substrate_region(self) -> kdb.Region:
336 """
337 The substrate below the layout (and its halo), the bottom plate of the capacitances to the substrate,
338 split into its wells, of the nets of the wells (e.g. VDD), and the rest, of the substrate (VSUBS)
340 NOTE: with --blackbox, its shapes are plates of the capacitor devices with a terminal on the substrate
341 (e.g. a VPP cap), see DeviceCapacitorPlates
342 """
343 dbu = self.pex_context.dbu
344 side_halo_um = self.tech_info.tech.process_parasitics.side_halo
345 rest = kdb.Region(self.pex_context.top_cell_bbox().enlarged(side_halo_um / dbu)) # e.g. 8 µm halo
347 # NOTE: the channels of the transistors aren't substrate the gate poly couples to,
348 # their capacitances are the transistor models' (#290)
349 rest -= self.pex_context.mos_channels
351 substrate_region = kdb.Region()
352 substrate_region.enable_properties()
354 plate_properties = {DEVICE_CAPACITOR_PLATE_PROPERTY: True} if self.pex_context.blackbox_devices else {}
356 def insert(region: kdb.Region, net_name: NetName):
357 for polygon in region.each():
358 substrate_region.insert(kdb.PolygonWithProperties(polygon.downcast(),
359 {'net': net_name, **plate_properties}))
361 # NOTE: where wells overlap (e.g. an nwell within a deep nwell), the first one is at the surface
362 for well_layer_name in self.tech_info.tech.substrate.well_lvs_layer_names:
363 gds_pair = self.gds_pair(layer_name=well_layer_name)
364 if gds_pair is None:
365 continue
366 # NOTE: the well's LVS layer only, not the others of its GDS pair (e.g. the pwell around an isolated pwell)
367 wells = self.pex_context.shapes_of_layer(gds_pair=gds_pair, lvs_layer_names=[well_layer_name])
368 if wells is None:
369 continue
370 for well in wells.merged().each(): # NOTE: merged per net
371 well_region = kdb.Region(well.downcast()) & rest
372 rest -= well_region
373 insert(well_region, well.property('net'))
375 insert(rest, self.tech_info.internal_substrate_layer_name)
376 return substrate_region
378 def extract(self) -> ExtractionResults:
379 extraction_results = ExtractionResults()
381 # TODO: for now, we always flatten and have only 1 cell
382 cell_name = self.pex_context.annotated_top_cell.name
383 extraction_report = ExtractionReporter(cell_name=cell_name,
384 dbu=self.pex_context.dbu,
385 report_caps=self.report_caps)
386 # NOTE: in RC mode, the capacitances go onto the resistor network, by their geometry
387 cell_extraction_results = CellExtractionResults(
388 cell_name=cell_name,
389 keep_capacitance_geometry=self.pex_mode == PEXMode.RC,
390 substrate_layer_name=self.tech_info.internal_substrate_layer_name,
391 substrate_well_layer_names=self.tech_info.substrate_well_layer_names,
392 exclusions=self.exclusions
393 )
395 # Explicitly log the stacktrace here, because otherwise Exceptions
396 # raised in the callbacks of *NeighborhoodVisitors can cause RuntimeErrors
397 # that are not traceable beyond the Region.complex_op() calls
398 try:
399 self.extract_cell(results=cell_extraction_results,
400 report=extraction_report)
401 except RuntimeError as e:
402 import traceback
403 print(f"Caught a RuntimeError: {e}")
404 traceback.print_exc()
405 raise
407 extraction_results.cell_extraction_results[cell_name] = cell_extraction_results
409 extraction_report.save(self.report_path)
411 return extraction_results
413 def extract_cell(self,
414 results: CellExtractionResults,
415 report: ExtractionReporter):
416 netlist: kdb.Netlist = self.pex_context.lvsdb.netlist()
417 dbu = self.pex_context.dbu
419 if self.exclusions is not None and not self.pex_mode.need_resistance():
420 warn_of_ports_without_nodes(self.exclusions, f"--mode {self.pex_mode}")
421 # ------------------------------------------------------------------------
423 layer_regions_by_name: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
425 all_region = kdb.Region()
426 all_region.enable_properties()
428 layer_regions_by_name[self.tech_info.internal_substrate_layer_name] = self.substrate_region()
430 # NOTE: the diffusion (the source/drain of the transistors) is above the substrate, which it shields,
431 # and below the metal layers, of whose capacitances it's the bottom plate
432 for diffusion_layer in self.tech_info.process_diffusion_layers:
433 diffusion_shapes = self.shapes_of_layer(diffusion_layer.name)
434 if diffusion_shapes is not None:
435 diffusion_shapes.enable_properties()
436 gds_pair = self.gds_pair(diffusion_layer.name)
437 canonical_layer_name = self.tech_info.canonical_layer_name_by_gds_pair[gds_pair]
438 layer_regions_by_name[canonical_layer_name] += diffusion_shapes
439 layer_regions_by_name[canonical_layer_name].enable_properties()
441 via_name_below_layer_name: Dict[LayerName, Optional[LayerName]] = {}
442 via_name_above_layer_name: Dict[LayerName, Optional[LayerName]] = {}
443 via_regions_by_via_name: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
445 previous_via_name: Optional[str] = None
447 for metal_layer in self.tech_info.process_metal_layers:
448 layer_name = metal_layer.name
449 gds_pair = self.gds_pair(layer_name)
450 canonical_layer_name = self.tech_info.canonical_layer_name_by_gds_pair[gds_pair]
452 # NOTE: with --blackbox, the plates of the capacitor devices are marked (see DeviceCapacitorPlates)
453 all_layer_shapes = self.shapes_of_layer(layer_name,
454 mark_device_capacitor_plates=self.pex_context.blackbox_devices)
455 if all_layer_shapes is not None:
456 all_layer_shapes.enable_properties()
458 layer_regions_by_name[canonical_layer_name] += all_layer_shapes
459 layer_regions_by_name[canonical_layer_name].enable_properties()
460 all_region += all_layer_shapes
462 if metal_layer.metal_layer.HasField('contact_above'):
463 contact = metal_layer.metal_layer.contact_above
465 via_regions = self.shapes_of_layer(contact.name)
466 if via_regions is not None:
467 via_regions.enable_properties()
468 via_regions_by_via_name[contact.name] += via_regions
469 via_name_above_layer_name[canonical_layer_name] = contact.name
470 via_name_below_layer_name[canonical_layer_name] = previous_via_name
472 previous_via_name = contact.name
473 else:
474 previous_via_name = None
476 all_layer_names = list(layer_regions_by_name.keys())
478 # ------------------------------------------------------------------------
479 # NOTE: before the capacitances, which go by the pieces of the nets with ports at exclusion markers (NetPiece)
480 if self.pex_mode.need_resistance():
481 c: kdb.Circuit = netlist.top_circuit()
482 info(f"LVSDB: found {c.pin_count()}pins")
484 # FIXME:
485 # currenly, tesselation does not work:
486 # https://github.com/KLayout/klayout/issues/2100
487 r_extractor = RExtractor(pex_context=self.pex_context,
488 substrate_algorithm=pb_RExtractorTech.Algorithm.ALGORITHM_SQUARE_COUNTING,
489 #substrate_algorithm = pb_RExtractorTech.Algorithm.ALGORITHM_TESSELATION,
490 wire_algorithm = pb_RExtractorTech.Algorithm.ALGORITHM_SQUARE_COUNTING,
491 delaunay_b = self.delaunay_b,
492 delaunay_amax = self.delaunay_amax,
493 via_merge_distance = 0,
494 skip_simplify = True,
495 exclusions = self.exclusions)
496 rex_request = r_extractor.prepare_request()
497 report.output_rex_request(request=rex_request)
499 rex_result = r_extractor.extract(rex_request)
500 report.output_rex_result(result=rex_result)
502 #
503 # node_by_id: Dict[int, r_network_pb2.RNode] = {}
504 # subproc("\tNodes:")
505 # for node in rex_result.nodes:
506 # node_by_id[node.node_id] = node
507 #
508 # msg = f"\t\tNode #{hex(node.node_id)} '{node.node_name}' " \
509 # f"of net '{node.net_name}' " \
510 # f"on layer '{node.layer_name}' "
511 # match node.location.kind:
512 # case location_pb2.Location.Kind.LOCATION_KIND_POINT:
513 # p = node.location.point
514 # msg += f"at {p.x},{p.y} ({p.x * dbu} µm, {p.y * dbu} µm)"
515 # case location_pb2.Location.Kind.LOCATION_KIND_BOX:
516 # b = node.location.box
517 # msg += f"at {b.lower_left.x},{b.lower_left.y};{b.upper_right.x},{b.upper_right.y} (" \
518 # f"B/L {round(b.lower_left.x * dbu, 3)},"\
519 # f"{round(b.lower_left.y * dbu, 3)} µm, " \
520 # f"T/R {round(b.upper_right.x * dbu, 3)},"\
521 # f"{round(b.upper_right.y * dbu)} µm)"
522 # subproc(msg)
523 #
524 # subproc("\tElements:")
525 # for element in rex_result.elements:
526 # node_a = node_by_id[element.node_a.node_id]
527 # node_b = node_by_id[element.node_b.node_id]
528 # subproc(f"\t\t{node_a.node_name} (port net '{node_a.net_name}') "
529 # f"↔︎ {node_b.node_name} (port net '{node_b.net_name}') "
530 # f"{round(element.resistance, 3)} Ω")
532 results.r_extraction_result = rex_result
534 nets_joined_by_substrate = results.nets_joined_by_substrate()
535 if nets_joined_by_substrate:
536 warning("Only the substrate, a well or a tap joins pieces of the metal of these nets, which the "
537 "resistance network has no resistance for, so the pieces are ideally on their nets: " +
538 ', '.join(nets_joined_by_substrate))
539 log_peak_memory('the resistance extraction')
541 # ------------------------------------------------------------------------
542 if self.pex_mode.need_capacitance():
543 # NOTE: a layer pair without an overlap capacitance would miss its capacitances,
544 # which is never intended (#217), e.g. the plates of a MIM cap in white-box mode,
545 # but for the capacitances of the devices (e.g. of the diffusion to the substrate)
546 missing_overlap_caps = [f"{top_layer_name} over {bottom_layer_name}"
547 for idx, bottom_layer_name in enumerate(all_layer_names)
548 for top_layer_name in all_layer_names[idx + 1:]
549 if not self.tech_info.overlap_cap_by_layer_names
550 .get(top_layer_name, {}).get(bottom_layer_name, None)
551 and not self.tech_info.is_device_capacitance(top_layer_name, bottom_layer_name)]
552 if missing_overlap_caps:
553 raise CExtractionTechError(
554 "The tech info has no overlap capacitance for these layer pairs of the layout, "
555 "so their capacitances would be missing (e.g. the plates of a MIM cap, "
556 "which --blackbox leaves to the device model):\n" +
557 '\n'.join(f" - {pair}" for pair in missing_overlap_caps)
558 )
560 # NOTE: with --blackbox, the capacitances between the plates of a capacitor device
561 # (e.g. the fingers of a MOM cap) are left to its model,
562 # and those to the substrate, if the substrate is a terminal of the device (e.g. of a VPP cap)
563 device_capacitor_plates = DeviceCapacitorPlates.from_circuit(
564 circuit=self.pex_context.top_circuit,
565 device_class_names=self.tech_info.device_models.metal_capacitor_class_names,
566 substrate_net_name=self.pex_context.substrate_net_name,
567 substrate_shapes_net_name=self.tech_info.internal_substrate_layer_name
568 ) if self.pex_context.blackbox_devices else DeviceCapacitorPlates()
570 overlap_extractor = OverlapExtractor(
571 all_layer_names=all_layer_names,
572 layer_regions_by_name=layer_regions_by_name,
573 dbu=dbu,
574 tech_info=self.tech_info,
575 device_capacitor_plates=device_capacitor_plates,
576 results=results,
577 report=report
578 )
579 overlap_extractor.extract()
581 sidewall_and_fringe_extractor = SidewallAndFringeExtractor(
582 all_layer_names=all_layer_names,
583 layer_regions_by_name=layer_regions_by_name,
584 dbu=dbu,
585 scale_ratio_to_fit_halo=self.scale_ratio_to_fit_halo,
586 tech_info=self.tech_info,
587 device_capacitor_plates=device_capacitor_plates,
588 mos_channels=self.pex_context.mos_channels,
589 results=results,
590 report=report
591 )
592 sidewall_and_fringe_extractor.extract()
593 log_peak_memory('the capacitance extraction')
595 return results