Coverage for klayout_pex/klayout/lvsdb_extractor.py: 95%

397 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-02 23:32 +0000

1# 

2# -------------------------------------------------------------------------------- 

3# SPDX-FileCopyrightText: 2024-2025 Martin Jan Köhler and Harald Pretl 

4# Johannes Kepler University, Institute for Integrated Circuits. 

5# 

6# This file is part of KPEX  

7# (see https://github.com/iic-jku/klayout-pex). 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21# SPDX-License-Identifier: GPL-3.0-or-later 

22# -------------------------------------------------------------------------------- 

23# 

24from __future__ import annotations 

25 

26from collections import defaultdict 

27from dataclasses import dataclass 

28from functools import cached_property 

29import tempfile 

30from typing import * 

31 

32from rich.pretty import pprint 

33 

34import klayout.db as kdb 

35 

36from ..log import ( 

37 console, 

38 debug, 

39 info, 

40 warning, 

41 error, 

42 rule 

43) 

44 

45from .shapes_pb2_converter import ShapesConverter 

46 

47from ..tech_info import TechInfo 

48import klayout_pex_protobuf.kpex.geometry.shapes_pb2 as shapes_pb2 

49import klayout_pex_protobuf.kpex.layout.device_pb2 as device_pb2 

50import klayout_pex_protobuf.kpex.layout.pin_pb2 as pin_pb2 

51import klayout_pex_protobuf.kpex.layout.location_pb2 as location_pb2 

52import klayout_pex_protobuf.kpex.tech.tech_pb2 as tech_pb2 

53 

54GDSPair = Tuple[int, int] 

55 

56LayerIndexMap = Dict[int, int] # maps layer indexes of LVSDB to annotated_layout 

57LVSDBRegions = Dict[int, kdb.Region] # maps layer index of annotated_layout to LVSDB region 

58 

59# the property of the shapes of the plates of a device capacitor (see KLayoutExtractionContext.shapes_of_layer) 

60DEVICE_CAPACITOR_PLATE_PROPERTY = 'device_capacitor_plate' 

61 

62 

63class LVSDBError(Exception): 

64 """ 

65 The LVS database lacks what the extraction depends on. 

66 """ 

67 pass 

68 

69 

70def unique_name(name: str, present_names: Set[str], separator: str = '$') -> str: 

71 """ 

72 The name, or if it's present, the name with a number (e.g. vss$1), 

73 like KLayout's tl::unique_name, which its SPICE writer names the nets with 

74 """ 

75 if name not in present_names: 

76 return name 

77 j = 0 

78 m = 1 << 30 

79 while m > 0: 

80 j += m 

81 if f"{name}{separator}{j}" not in present_names: 

82 j -= m 

83 m >>= 1 

84 return f"{name}{separator}{j + 1}" 

85 

86 

87@dataclass 

88class KLayoutExtractedLayerInfo: 

89 index: int 

90 lvs_layer_name: str # NOTE: this can be computed, so gds_pair is preferred 

91 gds_pair: GDSPair 

92 region: kdb.Region 

93 

94 

95@dataclass 

96class KLayoutMergedExtractedLayerInfo: 

97 source_layers: List[KLayoutExtractedLayerInfo] 

98 gds_pair: GDSPair 

99 

100 

101@dataclass 

102class KLayoutNonemptyExtractedLayers: 

103 extracted_layers: Dict[GDSPair, KLayoutMergedExtractedLayerInfo] 

104 unnamed_layers: List[KLayoutExtractedLayerInfo] # not in the tech info 

105 unmodeled_layers: List[str] # unnamed layers the tech info has no layer for 

106 

107 

108@dataclass 

109class KLayoutExtractionContext: 

110 lvsdb: kdb.LayoutToNetlist 

111 tech: TechInfo 

112 dbu: float 

113 layer_index_map: LayerIndexMap 

114 lvsdb_regions: LVSDBRegions 

115 cell_mapping: kdb.CellMapping 

116 annotated_top_cell: kdb.Cell 

117 annotated_layout: kdb.Layout 

118 extracted_layers: Dict[GDSPair, KLayoutMergedExtractedLayerInfo] 

119 unnamed_layers: List[KLayoutExtractedLayerInfo] 

120 # unnamed layers the tech info has no layer for (derived from the same original layer) 

121 unmodeled_layers: List[str] 

122 # the devices are black-boxed (--blackbox), their models have their capacitances (see ComputedLayerInfo.Kind) 

123 blackbox_devices: bool 

124 

125 @classmethod 

126 def prepare_extraction(cls, 

127 lvsdb: kdb.LayoutToNetlist, 

128 top_cell: str, 

129 tech: TechInfo, 

130 blackbox_devices: bool) -> KLayoutExtractionContext: 

131 # NOTE: e.g. the abstract of a device the LVS script removed from the netlist is left as a top cell 

132 internal_top_cell = lvsdb.internal_top_cell() 

133 extra_top_cells = [c.name for c in lvsdb.internal_layout().top_cells() 

134 if c.cell_index() != internal_top_cell.cell_index()] 

135 if extra_top_cells: 

136 warning(f"Ignoring the LVS database top cells besides {internal_top_cell.name}: " 

137 f"{', '.join(extra_top_cells)}") 

138 

139 cls.make_net_names_unique(lvsdb.netlist()) 

140 

141 dbu = lvsdb.internal_layout().dbu 

142 annotated_layout = kdb.Layout() 

143 annotated_layout.dbu = dbu 

144 top_cell = annotated_layout.create_cell(top_cell) 

145 

146 # CellMapping 

147 # mapping of internal layout to target layout for the circuit mapping 

148 # https://www.klayout.de/doc-qt5/code/class_CellMapping.html 

149 # --- 

150 # https://www.klayout.de/doc-qt5/code/class_LayoutToNetlist.html#method18 

151 # Creates a cell mapping for copying shapes from the internal layout to the given target layout 

152 cm = lvsdb.cell_mapping_into(annotated_layout, # target layout 

153 top_cell, 

154 not blackbox_devices) # with_device_cells 

155 

156 lvsdb_regions, layer_index_map = cls.build_LVS_layer_map(annotated_layout=annotated_layout, 

157 lvsdb=lvsdb, 

158 tech=tech, 

159 blackbox_devices=blackbox_devices) 

160 

161 # NOTE: GDS only supports integer properties to GDS, 

162 # as GDS does not support string keys, 

163 # like OASIS does. 

164 net_name_prop = "net" 

165 

166 # Build a full hierarchical representation of the nets 

167 # https://www.klayout.de/doc-qt5/code/class_LayoutToNetlist.html#method14 

168 # hier_mode = None 

169 hier_mode = kdb.LayoutToNetlist.BuildNetHierarchyMode.BNH_Flatten 

170 # hier_mode = kdb.LayoutToNetlist.BuildNetHierarchyMode.BNH_SubcircuitCells 

171 

172 lvsdb.build_all_nets( 

173 cmap=cm, # mapping of internal layout to target layout for the circuit mapping 

174 target=annotated_layout, # target layout 

175 lmap=lvsdb_regions, # maps: target layer index => net regions 

176 hier_mode=hier_mode, # hier mode 

177 netname_prop=net_name_prop, # property name to which to attach the net name 

178 circuit_cell_name_prefix="CIRCUIT_", # NOTE: generates a cell for each circuit 

179 net_cell_name_prefix=None, # NOTE: this would generate a cell for each net 

180 device_cell_name_prefix=None # NOTE: this would create a cell for each device (e.g. transistor) 

181 ) 

182 

183 nonempty_layers = cls.nonempty_extracted_layers(lvsdb=lvsdb, 

184 tech=tech, 

185 annotated_layout=annotated_layout, 

186 layer_index_map=layer_index_map, 

187 blackbox_devices=blackbox_devices) 

188 

189 return KLayoutExtractionContext( 

190 lvsdb=lvsdb, 

191 tech=tech, 

192 dbu=dbu, 

193 annotated_top_cell=top_cell, 

194 layer_index_map=layer_index_map, 

195 lvsdb_regions=lvsdb_regions, 

196 cell_mapping=cm, 

197 annotated_layout=annotated_layout, 

198 extracted_layers=nonempty_layers.extracted_layers, 

199 unnamed_layers=nonempty_layers.unnamed_layers, 

200 unmodeled_layers=nonempty_layers.unmodeled_layers, 

201 blackbox_devices=blackbox_devices 

202 ) 

203 

204 @staticmethod 

205 def make_net_names_unique(netlist: kdb.Netlist): 

206 """ 

207 Names each net of a circuit uniquely, as KLayout's SPICE writer does (e.g. vss, vss$1) 

208 

209 NOTE: nets can share a name, e.g. the metal islands of a supply, joined only in the parent 

210 (unless the LVS script connects them implicitly), but the extraction identifies a net by its name, 

211 and the annotated layout has the name on the shapes of the net (#211 §10) 

212 

213 NOTE: a net without a label gets its expanded name (e.g. $2) as its name, as KLayout 0.30.4 reads it 

214 from the LVSDB without one (newer versions with the expanded name) 

215 """ 

216 for circuit in netlist.each_circuit(): 

217 names: Set[str] = set() 

218 for net in list(circuit.each_net()): 

219 name = unique_name(net.expanded_name(), names) 

220 names.add(name) 

221 if name != net.name: 

222 net.name = name 

223 

224 @staticmethod 

225 def build_LVS_layer_map(annotated_layout: kdb.Layout, 

226 lvsdb: kdb.LayoutToNetlist, 

227 tech: TechInfo, 

228 blackbox_devices: bool) -> Tuple[LVSDBRegions, LayerIndexMap]: 

229 # NOTE: currently, the layer numbers are auto-assigned 

230 # by the sequence they occur in the LVS script, hence not well defined! 

231 # build a layer map for the layers that correspond to original ones. 

232 

233 # https://www.klayout.de/doc-qt5/code/class_LayerInfo.html 

234 lvsdb_regions: LVSDBRegions = {} 

235 layer_index_map: LayerIndexMap = {} 

236 

237 if not hasattr(lvsdb, "layer_indexes"): 

238 raise Exception("Needs at least KLayout version 0.29.2") 

239 

240 for layer_index in lvsdb.layer_indexes(): 

241 lname = lvsdb.layer_name(layer_index) 

242 

243 computed_layer_info = tech.computed_layer_info_by_name.get(lname, None) 

244 if computed_layer_info and blackbox_devices: 

245 match computed_layer_info.kind: 

246 case tech_pb2.ComputedLayerInfo.Kind.KIND_DEVICE_RESISTOR: 

247 continue 

248 case tech_pb2.ComputedLayerInfo.Kind.KIND_DEVICE_CAPACITOR: 

249 continue 

250 

251 gds_pair = tech.gds_pair_for_computed_layer_name.get(lname, None) 

252 if not gds_pair: 

253 li = lvsdb.internal_layout().get_info(layer_index) 

254 if li != kdb.LayerInfo(): 

255 gds_pair = (li.layer, li.datatype) 

256 

257 if gds_pair is not None: 

258 annotated_layer_index = annotated_layout.layer() # creates new index each time! 

259 # Creates a new internal layer! because multiple layers with the same gds_pair are possible! 

260 annotated_layout.set_info(annotated_layer_index, kdb.LayerInfo(*gds_pair)) 

261 region = lvsdb.layer_by_index(layer_index) 

262 lvsdb_regions[annotated_layer_index] = region 

263 layer_index_map[layer_index] = annotated_layer_index 

264 

265 return lvsdb_regions, layer_index_map 

266 

267 @staticmethod 

268 def nonempty_extracted_layers(lvsdb: kdb.LayoutToNetlist, 

269 tech: TechInfo, 

270 annotated_layout: kdb.Layout, 

271 layer_index_map: LayerIndexMap, 

272 blackbox_devices: bool) -> KLayoutNonemptyExtractedLayers: 

273 # https://www.klayout.de/doc-qt5/code/class_LayoutToNetlist.html#method18 

274 nonempty_layers: Dict[GDSPair, KLayoutMergedExtractedLayerInfo] = {} 

275 

276 unnamed_layers: List[KLayoutExtractedLayerInfo] = [] 

277 original_gds_pair_by_unnamed_layer_name: Dict[str, GDSPair] = {} 

278 lvsdb_layer_indexes = lvsdb.layer_indexes() 

279 for idx, ln in enumerate(lvsdb.layer_names()): 

280 li = lvsdb_layer_indexes[idx] 

281 if li not in layer_index_map: 

282 continue 

283 li = layer_index_map[li] 

284 layer = kdb.Region(annotated_layout.top_cell().begin_shapes_rec(li)) 

285 layer.enable_properties() 

286 if layer.count() >= 1: 

287 computed_layer_info = tech.computed_layer_info_by_name.get(ln, None) 

288 if not computed_layer_info: 

289 gds_pair = (1000 + idx, 20) 

290 linfo = KLayoutExtractedLayerInfo( 

291 index=idx, 

292 lvs_layer_name=ln, 

293 gds_pair=gds_pair, 

294 region=layer 

295 ) 

296 unnamed_layers.append(linfo) 

297 original_info = annotated_layout.get_info(li) 

298 original_gds_pair_by_unnamed_layer_name[ln] = (original_info.layer, original_info.datatype) 

299 continue 

300 

301 if blackbox_devices: 

302 match computed_layer_info.kind: 

303 case tech_pb2.ComputedLayerInfo.Kind.KIND_DEVICE_RESISTOR: 

304 continue 

305 case tech_pb2.ComputedLayerInfo.Kind.KIND_DEVICE_CAPACITOR: 

306 continue 

307 

308 gds_pair = (computed_layer_info.layer_info.drw_gds_pair.layer, 

309 computed_layer_info.layer_info.drw_gds_pair.datatype) 

310 

311 linfo = KLayoutExtractedLayerInfo( 

312 index=idx, 

313 lvs_layer_name=ln, 

314 gds_pair=gds_pair, 

315 region=layer 

316 ) 

317 

318 entry = nonempty_layers.get(gds_pair, None) 

319 if entry: 

320 entry.source_layers.append(linfo) 

321 else: 

322 nonempty_layers[gds_pair] = KLayoutMergedExtractedLayerInfo( 

323 source_layers=[linfo], 

324 gds_pair=gds_pair, 

325 ) 

326 

327 unmodeled_layers = KLayoutExtractionContext.check_unnamed_layers( 

328 lvsdb=lvsdb, 

329 tech=tech, 

330 annotated_layout=annotated_layout, 

331 layer_index_map=layer_index_map, 

332 unnamed_layers=unnamed_layers, 

333 original_gds_pair_by_unnamed_layer_name=original_gds_pair_by_unnamed_layer_name 

334 ) 

335 

336 return KLayoutNonemptyExtractedLayers(extracted_layers=nonempty_layers, 

337 unnamed_layers=unnamed_layers, 

338 unmodeled_layers=unmodeled_layers) 

339 

340 @staticmethod 

341 def check_unnamed_layers(lvsdb: kdb.LayoutToNetlist, 

342 tech: TechInfo, 

343 annotated_layout: kdb.Layout, 

344 layer_index_map: LayerIndexMap, 

345 unnamed_layers: List[KLayoutExtractedLayerInfo], 

346 original_gds_pair_by_unnamed_layer_name: Dict[str, GDSPair]) -> List[str]: 

347 """ 

348 An LVS layer the tech info doesn't know is left out of the extraction. 

349 That's fine where the tech info has layers derived from the same original layer 

350 that cover it (e.g. the raw contact layer, and the contacts to diffusion and poly). 

351 

352 :return: the layers the tech info has no derived layer for, which the LVS deck defines 

353 """ 

354 lvsdb_layer_index_by_name = {lvsdb.layer_name(li): li for li in lvsdb.layer_indexes()} 

355 

356 def region(lvs_layer_name: str) -> kdb.Region: 

357 li = layer_index_map.get(lvsdb_layer_index_by_name[lvs_layer_name], None) 

358 if li is None: 

359 return kdb.Region() 

360 return kdb.Region(annotated_layout.top_cell().begin_shapes_rec(li)) 

361 

362 unmodeled_layers: List[str] = [] 

363 partly_covered_layers: List[str] = [] 

364 

365 for ul in unnamed_layers: 

366 gds_pair = original_gds_pair_by_unnamed_layer_name[ul.lvs_layer_name] 

367 original_layer = tech.layer_info_by_gds_pair.get(gds_pair, None) 

368 description = f"{ul.lvs_layer_name} ({original_layer.name if original_layer else '%d/%d' % gds_pair})" 

369 

370 # NOTE: the LVS deck may not define the layers the tech info derives (e.g. differently named ones) 

371 derived_layer_names = [] if original_layer is None else [ 

372 name for name, info in tech.computed_layer_info_by_name.items() 

373 if info.original_layer_name == original_layer.name and name in lvsdb_layer_index_by_name 

374 ] 

375 if not derived_layer_names: 

376 unmodeled_layers.append(f"{description}: the tech info has no layer derived from it") 

377 continue 

378 

379 covered = kdb.Region() 

380 for name in derived_layer_names: 

381 covered += region(name) 

382 uncovered = ul.region.dup() 

383 uncovered.remove_properties() 

384 uncovered -= covered 

385 if uncovered.is_empty(): 

386 debug(f"The LVS layer {description} is covered by {', '.join(derived_layer_names)}") 

387 else: 

388 partly_covered_layers.append(f"{description}: {uncovered.count()} of {ul.region.count()} shapes " 

389 f"are not within {', '.join(derived_layer_names)}") 

390 

391 if unmodeled_layers or partly_covered_layers: 

392 warning("The extraction leaves out these LVS layers, as the tech info doesn't know them:\n" + 

393 '\n'.join(f" - {layer}" for layer in unmodeled_layers + partly_covered_layers)) 

394 

395 return unmodeled_layers 

396 

397 def top_cell_bbox(self) -> kdb.Box: 

398 b1: kdb.Box = self.annotated_layout.top_cell().bbox() 

399 b2: kdb.Box = self.lvsdb.internal_top_cell().bbox() 

400 if b1.area() > b2.area(): 

401 return b1 

402 else: 

403 return b2 

404 

405 def shapes_of_net(self, gds_pair: GDSPair, net: kdb.Net | str) -> Optional[kdb.Region]: 

406 lyr = self.extracted_layers.get(gds_pair, None) 

407 if not lyr: 

408 return None 

409 

410 shapes = kdb.Region() 

411 shapes.enable_properties() 

412 

413 requested_net_name = net.name if isinstance(net, kdb.Net) else net 

414 

415 def add_shapes_from_region(source_region: kdb.Region): 

416 iter, transform = source_region.begin_shapes_rec() 

417 while not iter.at_end(): 

418 shape = iter.shape() 

419 net_name = shape.property('net') 

420 if net_name == requested_net_name: 

421 shapes.insert(transform * # NOTE: this is a global/initial iterator-wide transformation 

422 iter.trans() * # NOTE: this is local during the iteration (due to sub hierarchy) 

423 shape.polygon) 

424 iter.next() 

425 

426 match len(lyr.source_layers): 

427 case 0: 

428 raise AssertionError('Internal error: Empty list of source_layers') 

429 case _: 

430 for sl in lyr.source_layers: 

431 add_shapes_from_region(sl.region) 

432 

433 return shapes 

434 

435 def shapes_of_layer(self, 

436 gds_pair: GDSPair, 

437 mark_device_capacitor_plates: bool = False) -> Optional[kdb.Region]: 

438 """ 

439 :param mark_device_capacitor_plates: give the shapes of the layers of KIND_DEVICE_CAPACITOR_PLATE 

440 (e.g. the fingers of a MOM cap) the property 

441 DEVICE_CAPACITOR_PLATE_PROPERTY, so that they are kept apart 

442 from the other shapes of their nets 

443 """ 

444 lyr = self.extracted_layers.get(gds_pair, None) 

445 if not lyr: 

446 return None 

447 

448 def is_plate(sl: KLayoutExtractedLayerInfo) -> bool: 

449 return mark_device_capacitor_plates and \ 

450 self.tech.computed_layer_info_by_name[sl.lvs_layer_name].kind == \ 

451 tech_pb2.ComputedLayerInfo.Kind.KIND_DEVICE_CAPACITOR_PLATE 

452 

453 shapes: kdb.Region 

454 

455 match len(lyr.source_layers): 

456 case 0: 

457 raise AssertionError('Internal error: Empty list of source_layers') 

458 case 1 if not is_plate(lyr.source_layers[0]): 

459 shapes = lyr.source_layers[0].region 

460 case _: 

461 # NOTE: currently a bug, for now use polygon-per-polygon workaround 

462 # shapes = kdb.Region() 

463 # for sl in lyr.source_layers: 

464 # shapes += sl.region 

465 shapes = kdb.Region() 

466 shapes.enable_properties() 

467 for sl in lyr.source_layers: 

468 plate_properties = {DEVICE_CAPACITOR_PLATE_PROPERTY: True} if is_plate(sl) else {} 

469 iter, transform = sl.region.begin_shapes_rec() 

470 while not iter.at_end(): 

471 p = kdb.PolygonWithProperties(iter.shape().polygon, 

472 {'net': iter.shape().property('net'), **plate_properties}) 

473 shapes.insert(transform * # NOTE: this is a global/initial iterator-wide transformation 

474 iter.trans() * # NOTE: this is local during the iteration (due to sub hierarchy) 

475 p) 

476 iter.next() 

477 

478 return shapes 

479 

480 def pins_of_layer(self, gds_pair: GDSPair) -> kdb.Region: 

481 pin_gds_pair = self.tech.layer_info_by_gds_pair[gds_pair].pin_gds_pair 

482 pin_gds_pair = pin_gds_pair.layer, pin_gds_pair.datatype 

483 # NOTE: the pin layer can have several LVS layers, where it's the drawn layer 

484 # (e.g. gf180mcuD, without pin layers, with Metal4 split under the MIM caps) 

485 pins = self.shapes_of_layer(pin_gds_pair) 

486 if pins is None: 

487 return kdb.Region() 

488 return pins 

489 

490 def labels_of_layer(self, gds_pair: GDSPair) -> kdb.Texts: 

491 labels_gds_pair = self.tech.layer_info_by_gds_pair[gds_pair].label_gds_pair 

492 labels_gds_pair = labels_gds_pair.layer, labels_gds_pair.datatype 

493 

494 lay: kdb.Layout = self.annotated_layout 

495 label_layer_idx = lay.find_layer(labels_gds_pair) # sky130 layer dt = 5 

496 if label_layer_idx is None: 

497 return kdb.Texts() 

498 

499 sh_it = lay.begin_shapes(self.lvsdb.internal_top_cell(), label_layer_idx) 

500 labels: kdb.Texts = kdb.Texts(sh_it) 

501 return labels 

502 

503 @cached_property 

504 def top_circuit(self) -> kdb.Circuit: 

505 return self.lvsdb.netlist().top_circuit() 

506 

507 @cached_property 

508 def substrate_net_name(self) -> str: 

509 """ 

510 The net the capacitances to the substrate go to: the substrate net of the LVS netlist, 

511 by a name of the tech info (e.g. sky130_gnd), or by a shape on its LVS layers, 

512 when a label names it otherwise (e.g. VSS, through the taps), else the first name, 

513 for a port of its own (e.g. a metal test pattern without taps or transistors) 

514 """ 

515 substrate = self.tech.tech.substrate 

516 for name in substrate.net_names: 

517 if self.top_circuit.net_by_name(name) is not None: 

518 return name 

519 

520 lvs_layer_names = set(self.lvsdb.layer_names()) 

521 for lvs_layer_name in substrate.lvs_layer_names: 

522 if lvs_layer_name not in lvs_layer_names: 

523 continue 

524 region = self.lvsdb.layer_by_name(lvs_layer_name) 

525 if region.is_empty(): 

526 continue 

527 # NOTE: a point within the first shape, the vertices of a trapezoid surround their average 

528 trapezoid = next(iter(region.each())).decompose_trapezoids()[0] 

529 points = list(trapezoid.each_point()) 

530 point = kdb.Point(sum(p.x for p in points) // len(points), sum(p.y for p in points) // len(points)) 

531 subcircuit_path: List[kdb.SubCircuit] = [] 

532 net = self.lvsdb.probe_net(region, point, subcircuit_path) 

533 # NOTE: the net of the shape may be within a subcircuit, the substrate is a pin of it 

534 for subcircuit in reversed(subcircuit_path): 

535 pins = list(net.each_pin()) if net is not None else [] 

536 net = subcircuit.net_for_pin(pins[0].pin_id()) if pins else None 

537 if net is not None: 

538 return net.expanded_name() 

539 

540 return substrate.net_names[0] if substrate.net_names else self.tech.internal_substrate_layer_name 

541 

542 @cached_property 

543 def devices_by_name(self) -> Dict[str, device_pb2.Device]: 

544 # NOTE: a device the LVS script created, rather than extracted, has no abstract, 

545 # so no terminal geometry to connect it to the resistor network 

546 # (e.g. a model mapping that replaces extracted devices with ones of another class) 

547 created_devices = [f"{d.expanded_name()} ({d.device_class().name})" 

548 for d in self.top_circuit.each_device() if d.device_abstract is None] 

549 if created_devices: 

550 raise LVSDBError(f"The LVS database has devices without layout geometry, " 

551 f"which the resistance extraction depends on: {', '.join(created_devices)}. " 

552 f"They were created by the LVS script rather than extracted from the layout, " 

553 f"e.g. to replace extracted devices with another device class") 

554 

555 dd = {} 

556 

557 shapes_converter = ShapesConverter(dbu=self.dbu) 

558 

559 # (device class, terminal, LVS layer) -> device names 

560 devices_by_unknown_terminal_layer: Dict[Tuple[str, str, str], List[str]] = defaultdict(list) 

561 

562 for d_kly in self.top_circuit.each_device(): 

563 # https://www.klayout.de/doc-qt5/code/class_Device.html 

564 d_kly: kdb.Device 

565 

566 d = device_pb2.Device() 

567 d.id = d_kly.id() 

568 d.device_name = d_kly.expanded_name() 

569 d.device_class_name = d_kly.device_class().name 

570 d.device_abstract_name = d_kly.device_abstract.name 

571 

572 for pd in d_kly.device_class().parameter_definitions(): 

573 p = d.parameters.add() 

574 p.id = pd.id() 

575 p.name = pd.name 

576 p.value = d_kly.parameter(pd.id()) 

577 

578 for td in d_kly.device_class().terminal_definitions(): 

579 n: kdb.Net = d_kly.net_for_terminal(td.id()) 

580 net_name = n.name or f"${n.cluster_id}" 

581 if n is None: 

582 warning(f"Skipping terminal {td.name} of device {d.name} ({d.device_class}) " 

583 f"is not connected to any net") 

584 terminal = d.terminals.add() 

585 terminal.id = td.id() 

586 terminal.name = td.name 

587 terminal.net_name = '' # TODO 

588 continue 

589 

590 for nt in n.each_terminal(): 

591 nt: kdb.NetTerminalRef 

592 

593 if nt.device().expanded_name() != d_kly.expanded_name(): 

594 continue 

595 if nt.terminal_id() != td.id(): 

596 continue 

597 

598 shapes_by_lyr_idx = self.lvsdb.shapes_of_terminal(nt) 

599 

600 terminal = d.terminals.add() 

601 terminal.device_id = d.id 

602 terminal.terminal_id = td.id() 

603 terminal.name = td.name 

604 terminal.net_name = net_name 

605 

606 def add_region(gds_pair: GDSPair, region: kdb.Region): 

607 region_by_layer = terminal.region_by_layer.add() 

608 # NOTE: the annotated layout has a layer for each LVS layer, so several for a GDS pair 

609 # (e.g. poly_con and poly_vpp), but the resistance extraction has the wires 

610 # of a GDS pair on one of them, so the terminal must be on that one to be a port 

611 region_by_layer.layer.id = self.annotated_layout.layer(*gds_pair) 

612 region_by_layer.layer.canonical_layer_name = self.tech.canonical_layer_name_by_gds_pair[gds_pair] 

613 shapes_converter.klayout_region_to_pb(region, region_by_layer.region) 

614 

615 for idx, shapes in shapes_by_lyr_idx.items(): 

616 lyr_idx = self.layer_index_map.get(idx, None) 

617 if lyr_idx is None: 

618 # NOTE: the tech info has no layer for the LVS layer of the terminal 

619 # (e.g. the ports of a MOM cap, the metal pins within its marker), 

620 # so the terminal is where it overlaps a conductor of its net, 

621 # which LVS connected it to (e.g. the metal of the pins) 

622 gds_pair_and_region = self.terminal_region_on_net(shapes, net_name) 

623 if gds_pair_and_region is None: 

624 key = (d.device_class_name, td.name, self.lvsdb.layer_name(idx)) 

625 devices_by_unknown_terminal_layer[key].append(d.device_name) 

626 else: 

627 add_region(*gds_pair_and_region) 

628 continue 

629 

630 lyr_info: kdb.LayerInfo = self.annotated_layout.layer_infos()[lyr_idx] 

631 add_region((lyr_info.layer, lyr_info.datatype), shapes) 

632 

633 dd[d.device_name] = d 

634 

635 # NOTE: a terminal without a node stays on its net, which a node of the resistance network carries 

636 # (#211 §6), so it misses only the resistance to where it is 

637 if devices_by_unknown_terminal_layer: 

638 def device_list(device_names: List[str]) -> str: 

639 listed = ', '.join(device_names[:3]) 

640 return listed if len(device_names) <= 3 else f"{listed} and {len(device_names) - 3} more" 

641 

642 warning("The resistance network has no nodes for these device terminals, " 

643 "as the tech info has no layer for their LVS layer, and they overlap no conductor of their net " 

644 "(e.g. a terminal on the substrate or a well):\n" + 

645 '\n'.join(f" - {device_class} terminal {terminal} on LVS layer {lvs_layer}: " 

646 f"{device_list(device_names)}" 

647 for (device_class, terminal, lvs_layer), device_names 

648 in sorted(devices_by_unknown_terminal_layer.items()))) 

649 

650 return dd 

651 

652 def terminal_region_on_net(self, 

653 terminal_shapes: kdb.Region, 

654 net_name: str) -> Optional[Tuple[GDSPair, kdb.Region]]: 

655 """ 

656 :return: the part of the terminal shapes on the conductor of its net they overlap most, 

657 the lowest of equals, or None if they overlap no conductor of their net 

658 

659 NOTE: one conductor, as the ports of a terminal are one node, which would short the resistance 

660 between the conductors (e.g. a pin on the top metal of a MOM cap, over the fingers below) 

661 """ 

662 lvsdb_layer_indexes = self.lvsdb.layer_indexes() 

663 bbox = terminal_shapes.bbox() 

664 best: Optional[Tuple[float, GDSPair, kdb.Region]] = None 

665 for gds_pair in self.tech.process_conductor_gds_pairs: 

666 lyr = self.extracted_layers.get(gds_pair, None) 

667 if lyr is None: 

668 continue 

669 net_shapes = kdb.Region() 

670 for sl in lyr.source_layers: 

671 computed_layer_info = self.tech.computed_layer_info_by_name[sl.lvs_layer_name] 

672 if computed_layer_info.kind == tech_pb2.ComputedLayerInfo.Kind.KIND_PIN: 

673 continue 

674 annotated_layer_index = self.layer_index_map[lvsdb_layer_indexes[sl.index]] 

675 # NOTE: the shapes near the terminal only, the net may be huge (e.g. a supply) 

676 iter = self.annotated_top_cell.begin_shapes_rec_touching(annotated_layer_index, bbox) 

677 while not iter.at_end(): 

678 shape = iter.shape() 

679 if shape.property('net') == net_name and not shape.is_text(): 

680 net_shapes.insert(iter.trans() * shape.polygon) 

681 iter.next() 

682 overlap = terminal_shapes & net_shapes 

683 area = overlap.area() 

684 if area > 0 and (best is None or area > best[0]): 

685 best = (area, gds_pair, overlap) 

686 return None if best is None else (best[1], best[2]) 

687 

688 @cached_property 

689 def pins_pb2_by_layer(self) -> Dict[GDSPair, List[pin_pb2.Pin]]: 

690 d = defaultdict(list) 

691 

692 for lvs_gds_pair, lyr_info in self.extracted_layers.items(): 

693 canonical_layer_name = self.tech.canonical_layer_name_by_gds_pair[lvs_gds_pair] 

694 # NOTE: LVS GDS Pair differs from real GDS Pair, 

695 # as in some cases we want to split a layer into different regions (ptap vs ntap, cap vs ncap) 

696 # so invent new datatype numbers, like adding 100 to the real GDS datatype 

697 gds_pair = self.tech.gds_pair_for_layer_name.get(canonical_layer_name, None) 

698 if gds_pair is None: 

699 continue 

700 if gds_pair not in self.tech.layer_info_by_gds_pair: 

701 continue 

702 

703 for lyr in lyr_info.source_layers: 

704 klayout_index = self.annotated_layout.layer(*lyr.gds_pair) 

705 

706 pins = self.pins_of_layer(gds_pair) 

707 labels = self.labels_of_layer(gds_pair) 

708 

709 pin_labels: kdb.Texts = labels & pins 

710 for l in pin_labels: 

711 l: kdb.Text 

712 # NOTE: because we want more like a point as a junction 

713 # and folx create huge pins (covering the whole metal) 

714 # we create our own "mini squares" 

715 # (ResistorExtractor will subtract the pins from the metal polygons, 

716 # so in the extreme case the polygons could become empty) 

717 

718 pin = pin_pb2.Pin() 

719 pin.label = l.string 

720 

721 pos = l.position() 

722 

723 # is there more elegant / faster way to do this? 

724 for p in pins: 

725 p: kdb.PolygonWithProperties 

726 if p.inside(pos): 

727 pin.net_name = p.property('net') 

728 break 

729 

730 canonical_layer_name = self.tech.canonical_layer_name_by_gds_pair[lyr.gds_pair] 

731 lvs_layer_name = self.tech.computed_layer_info_by_gds_pair[lyr.gds_pair].layer_info.name 

732 pin.layer.id = klayout_index 

733 pin.layer.canonical_layer_name = canonical_layer_name 

734 pin.layer.lvs_layer_name = lvs_layer_name 

735 

736 pin.label_point.x = pos.x 

737 pin.label_point.y = pos.y 

738 pin.label_point.net = pin.net_name 

739 

740 d[gds_pair].append(pin) 

741 

742 return d