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

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 

26import klayout.db as kdb 

27 

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 

49 

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 

57 

58 

59class CExtractionTechError(Exception): 

60 """ 

61 The tech info can't model the capacitances of the layout 

62 """ 

63 pass 

64 

65 

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) 

71 

72 

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) 

78 

79 

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) 

88 

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 {} 

94 

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 

106 

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() 

127 

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 

133 

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 

139 

140 

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) 

166 

167 

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 

177 

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) 

182 

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) 

213 

214 circuit = pex_context.top_circuit 

215 blackbox_devices = pex_context.blackbox_devices 

216 device_models = tech_info.device_models 

217 

218 def is_whiteboxed(device: kdb.Device) -> bool: 

219 return not blackbox_devices and RCX25NetlistExpander.is_whiteboxed(device.device_class(), device_models) 

220 

221 def is_removed(device: kdb.Device) -> bool: 

222 return device.id() in exclusions.removed_device_blocks or is_whiteboxed(device) 

223 

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()) 

228 

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)} 

231 

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))}") 

244 

245 substrate_lvs_layer_names = set(tech_info.tech.substrate.lvs_layer_names) 

246 

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() 

252 

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)) 

272 

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))}") 

280 

281 

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") 

289 

290 

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 

315 

316 if "PolygonWithProperties" not in kdb.__all__: 

317 raise Exception("KLayout version does not support properties (needs 0.30 at least)") 

318 

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) 

322 

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 

327 

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}") 

332 

333 return shapes 

334 

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) 

339 

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 

346 

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 

350 

351 substrate_region = kdb.Region() 

352 substrate_region.enable_properties() 

353 

354 plate_properties = {DEVICE_CAPACITOR_PLATE_PROPERTY: True} if self.pex_context.blackbox_devices else {} 

355 

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})) 

360 

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')) 

374 

375 insert(rest, self.tech_info.internal_substrate_layer_name) 

376 return substrate_region 

377 

378 def extract(self) -> ExtractionResults: 

379 extraction_results = ExtractionResults() 

380 

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 ) 

394 

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 

406 

407 extraction_results.cell_extraction_results[cell_name] = cell_extraction_results 

408 

409 extraction_report.save(self.report_path) 

410 

411 return extraction_results 

412 

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 

418 

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 # ------------------------------------------------------------------------ 

422 

423 layer_regions_by_name: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region) 

424 

425 all_region = kdb.Region() 

426 all_region.enable_properties() 

427 

428 layer_regions_by_name[self.tech_info.internal_substrate_layer_name] = self.substrate_region() 

429 

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() 

440 

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) 

444 

445 previous_via_name: Optional[str] = None 

446 

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] 

451 

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() 

457 

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 

461 

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

463 contact = metal_layer.metal_layer.contact_above 

464 

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 

471 

472 previous_via_name = contact.name 

473 else: 

474 previous_via_name = None 

475 

476 all_layer_names = list(layer_regions_by_name.keys()) 

477 

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") 

483 

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) 

498 

499 rex_result = r_extractor.extract(rex_request) 

500 report.output_rex_result(result=rex_result) 

501 

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)} Ω") 

531 

532 results.r_extraction_result = rex_result 

533 

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') 

540 

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 ) 

559 

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() 

569 

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() 

580 

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') 

594 

595 return results