Coverage for klayout_pex/rcx25/extractor.py: 91%

399 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-17 17:24 +0000

1#! /usr/bin/env python3 

2# 

3# -------------------------------------------------------------------------------- 

4# SPDX-FileCopyrightText: 2024 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/martinjankoehler/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 math 

27from collections import defaultdict 

28from typing import * 

29 

30import klayout.db as kdb 

31import klayout.rdb as rdb 

32 

33from ..klayout.lvsdb_extractor import KLayoutExtractionContext, GDSPair 

34from ..log import ( 

35 console, 

36 debug, 

37 info, 

38 warning, 

39 error 

40) 

41from ..tech_info import TechInfo 

42from .extraction_results import * 

43import klayout_pex_protobuf.process_stack_pb2 as process_stack_pb2 

44 

45 

46EdgeInterval = Tuple[float, float] 

47ChildIndex = int 

48EdgeNeighborhood = List[Tuple[EdgeInterval, Dict[ChildIndex, List[kdb.Polygon]]]] 

49 

50 

51class RCExtractor: 

52 def __init__(self, 

53 pex_context: KLayoutExtractionContext, 

54 tech_info: TechInfo, 

55 report_path: str): 

56 self.pex_context = pex_context 

57 self.tech_info = tech_info 

58 self.report_path = report_path 

59 

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

61 gds_pair = self.tech_info.gds_pair_for_computed_layer_name.get(layer_name, None) 

62 if not gds_pair: 

63 gds_pair = self.tech_info.gds_pair_for_layer_name.get(layer_name, None) 

64 if not gds_pair: 

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

66 return None 

67 return gds_pair 

68 

69 def shapes_of_net(self, layer_name: str, net: kdb.Net) -> Optional[kdb.Region]: 

70 gds_pair = self.gds_pair(layer_name=layer_name) 

71 if not gds_pair: 

72 return None 

73 

74 shapes = self.pex_context.shapes_of_net(gds_pair=gds_pair, net=net) 

75 if not shapes: 

76 debug(f"Nothing extracted for layer {layer_name}") 

77 return shapes 

78 

79 def shapes_of_layer(self, layer_name: str) -> Optional[kdb.Region]: 

80 gds_pair = self.gds_pair(layer_name=layer_name) 

81 if not gds_pair: 

82 return None 

83 

84 shapes = self.pex_context.shapes_of_layer(gds_pair=gds_pair) 

85 if not shapes: 

86 debug(f"Nothing extracted for layer {layer_name}") 

87 return shapes 

88 

89 def extract(self) -> ExtractionResults: 

90 extraction_results = ExtractionResults() 

91 

92 # TODO: for now, we always flatten and have only 1 cell 

93 cell_name = self.pex_context.top_cell.name 

94 report = rdb.ReportDatabase(f"PEX {cell_name}") 

95 cell_extraction_result = self.extract_cell(cell_name=cell_name, report=report) 

96 extraction_results.cell_extraction_results[cell_name] = cell_extraction_result 

97 

98 report.save(self.report_path) 

99 

100 return extraction_results 

101 

102 def extract_cell(self, 

103 cell_name: CellName, 

104 report: rdb.ReportDatabase) -> CellExtractionResults: 

105 lvsdb = self.pex_context.lvsdb 

106 netlist: kdb.Netlist = lvsdb.netlist() 

107 dbu = self.pex_context.dbu 

108 

109 extraction_results = CellExtractionResults(cell_name=cell_name) 

110 

111 rdb_cell = report.create_cell(cell_name) 

112 rdb_cat_common = report.create_category("Common") 

113 rdb_cat_sidewall_old = report.create_category("Sidewall (legacy space_check)") 

114 rdb_cat_sidewall = report.create_category("Sidewall (EdgeNeighborhoodVisitor)") 

115 rdb_cat_overlap = report.create_category("Overlap") 

116 rdb_cat_fringe = report.create_category("Fringe / Side Overlap") 

117 

118 def rdb_output(parent_category: rdb.RdbCategory, 

119 category_name: str, 

120 shapes: kdb.Shapes | kdb.Region | List[kdb.Edge]): 

121 rdb_cat = report.create_category(parent_category, category_name) 

122 report.create_items(rdb_cell.rdb_id(), ## TODO: if later hierarchical mode is introduced 

123 rdb_cat.rdb_id(), 

124 kdb.CplxTrans(mag=dbu), 

125 shapes) 

126 

127 circuit = netlist.circuit_by_name(self.pex_context.top_cell.name) 

128 # https://www.klayout.de/doc-qt5/code/class_Circuit.html 

129 if not circuit: 

130 circuits = [c.name for c in netlist.each_circuit()] 

131 raise Exception(f"Expected circuit called {self.pex_context.top_cell.name} in extracted netlist, " 

132 f"only available circuits are: {circuits}") 

133 

134 #---------------------------------------------------------------------------------------- 

135 layer2net2regions = defaultdict(lambda: defaultdict(kdb.Region)) 

136 net2layer2regions = defaultdict(lambda: defaultdict(kdb.Region)) 

137 layer_by_name: Dict[LayerName, process_stack_pb2.ProcessStackInfo.LayerInfo] = {} 

138 

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

140 all_region = kdb.Region() 

141 regions_below_layer: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region) 

142 regions_below_and_including_layer: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region) 

143 all_layer_names: List[LayerName] = [] 

144 layer_names_below: Dict[LayerName, List[LayerName]] = {} 

145 shielding_layer_names: Dict[Tuple[LayerName, LayerName], List[LayerName]] = defaultdict(list) 

146 previous_layer_name: Optional[str] = None 

147 

148 substrate_region = kdb.Region() 

149 substrate_region.insert(self.pex_context.top_cell_bbox().enlarged(8.0 / dbu)) # 8 µm halo 

150 substrate_layer_name = self.tech_info.internal_substrate_layer_name 

151 layer_names_below[substrate_layer_name] = [] 

152 all_layer_names.append(substrate_layer_name) 

153 layer2net2regions[substrate_layer_name][substrate_layer_name] = substrate_region 

154 net2layer2regions[substrate_layer_name][substrate_layer_name] = substrate_region 

155 layer_regions_by_name[substrate_layer_name] = substrate_region 

156 # NOTE: substrate not needed for 

157 # - all_region 

158 # - regions_below_layer 

159 # - regions_below_and_including_layer 

160 

161 for metal_layer in self.tech_info.process_metal_layers: 

162 layer_name = metal_layer.name 

163 gds_pair = self.gds_pair(layer_name) 

164 canonical_layer_name = self.tech_info.canonical_layer_name_by_gds_pair[gds_pair] 

165 

166 all_layer_shapes = self.shapes_of_layer(layer_name) or kdb.Region() 

167 layer_regions_by_name[canonical_layer_name] += all_layer_shapes 

168 # NOTE: multiple LVS layers can be mapped to the same canonical name 

169 if previous_layer_name != canonical_layer_name: 

170 regions_below_layer[canonical_layer_name] += all_region 

171 layer_names_below[canonical_layer_name] = list(all_layer_names) 

172 for ln in all_layer_names: 

173 lp = (canonical_layer_name, ln) 

174 shielding_layer_names[lp] = [l for l in all_layer_names 

175 if l != ln and l not in layer_names_below[ln]] 

176 shielding_layer_names[ln, canonical_layer_name] = shielding_layer_names[lp] 

177 all_layer_names.append(canonical_layer_name) 

178 all_region += all_layer_shapes 

179 regions_below_and_including_layer[canonical_layer_name] += all_region 

180 

181 previous_layer_name = canonical_layer_name 

182 

183 for net in circuit.each_net(): 

184 net_name = net.expanded_name() 

185 

186 shapes = self.shapes_of_net(layer_name=layer_name, net=net) 

187 if shapes: 

188 layer2net2regions[canonical_layer_name][net_name] += shapes 

189 net2layer2regions[net_name][canonical_layer_name] += shapes 

190 layer_by_name[canonical_layer_name] = metal_layer 

191 

192 shielded_regions_between_layers: Dict[Tuple[LayerName, LayerName], kdb.Region] = {} 

193 for top_layer_name in layer2net2regions.keys(): 

194 for bot_layer_name in reversed(layer_names_below[top_layer_name]): 

195 shielded_region = kdb.Region() 

196 shielding_layers = shielding_layer_names.get((top_layer_name, bot_layer_name), None) 

197 if shielding_layers: 

198 for sl in shielding_layers: 

199 shielded_region += layer_regions_by_name[sl] 

200 shielded_region.merge() 

201 shielded_regions_between_layers[(top_layer_name, bot_layer_name)] = shielded_region 

202 shielded_regions_between_layers[(bot_layer_name, top_layer_name)] = shielded_region 

203 if shielded_region: 

204 rdb_output(rdb_cat_common, f"Shielded ({top_layer_name}-{bot_layer_name})", shielded_region) 

205 

206 #---------------------------------------------------------------------------------------- 

207 

208 side_halo_um = self.tech_info.tech.process_parasitics.side_halo 

209 side_halo_dbu = int(side_halo_um / dbu) + 1 # add 1 nm to halo 

210 

211 space_markers_by_layer_name: Dict[LayerName, kdb.Region] = {} 

212 rdb_cat_space_markers = report.create_category(rdb_cat_sidewall_old, "All Space Markers") 

213 

214 for layer_name in layer2net2regions.keys(): 

215 if layer_name == substrate_layer_name: 

216 continue 

217 

218 space_markers = layer_regions_by_name[layer_name].space_check( 

219 d=side_halo_dbu, # min space in um 

220 whole_edges=True, # whole edges 

221 metrics=kdb.Metrics.Projection, # metrics 

222 ignore_angle=None, # ignore angle 

223 min_projection=None, # min projection 

224 max_projection=None, # max projection 

225 shielded=True, # shielding 

226 opposite_filter=kdb.Region.NoOppositeFilter, # error filter for opposite sides 

227 rect_filter=kdb.Region.NoRectFilter, # error filter for rect input shapes 

228 negative=False, # negative 

229 property_constraint=kdb.Region.IgnoreProperties, # property_constraint 

230 zero_distance_mode=kdb.Region.IncludeZeroDistanceWhenTouching # zero distance mode 

231 ) 

232 space_markers_by_layer_name[layer_name] = space_markers 

233 rdb_output(rdb_cat_space_markers, f"layer={layer_name}", space_markers) 

234 

235 # 

236 # (1) OVERLAP CAPACITANCE 

237 # 

238 for top_layer_name in layer2net2regions.keys(): 

239 if top_layer_name == substrate_layer_name: 

240 continue 

241 

242 top_net2regions = layer2net2regions.get(top_layer_name, None) 

243 if not top_net2regions: 

244 continue 

245 

246 top_overlap_specs = self.tech_info.overlap_cap_by_layer_names.get(top_layer_name, None) 

247 if not top_overlap_specs: 

248 warning(f"No overlap cap specified for layer top={top_layer_name}") 

249 continue 

250 

251 rdb_cat_top_layer = report.create_category(rdb_cat_overlap, f"top_layer={top_layer_name}") 

252 

253 shapes_top_layer = layer_regions_by_name[top_layer_name] 

254 

255 for bot_layer_name in reversed(layer_names_below[top_layer_name]): 

256 bot_net2regions = layer2net2regions.get(bot_layer_name, None) 

257 if not bot_net2regions: 

258 continue 

259 

260 overlap_cap_spec = top_overlap_specs.get(bot_layer_name, None) 

261 if not overlap_cap_spec: 

262 warning(f"No overlap cap specified for layer top={top_layer_name}/bottom={bot_layer_name}") 

263 continue 

264 

265 rdb_cat_bot_layer = report.create_category(rdb_cat_top_layer, f"bot_layer={bot_layer_name}") 

266 

267 shielded_region = shielded_regions_between_layers[(top_layer_name, bot_layer_name)].and_(shapes_top_layer) 

268 rdb_output(rdb_cat_bot_layer, "Shielded Between Layers Region", shielded_region) 

269 

270 for net_top in top_net2regions.keys(): 

271 shapes_top_net: kdb.Region = top_net2regions[net_top].dup() 

272 

273 for net_bot in bot_net2regions.keys(): 

274 if net_top == net_bot: 

275 continue 

276 

277 shapes_bot_net: kdb.Region = bot_net2regions[net_bot] 

278 

279 overlapping_shapes = shapes_top_net.__and__(shapes_bot_net) 

280 if overlapping_shapes: 

281 rdb_cat_nets = report.create_category(rdb_cat_bot_layer, f"{net_top} – {net_bot}") 

282 rdb_output(rdb_cat_nets, "Overlapping Shapes", overlapping_shapes) 

283 

284 shielded_net_shapes = overlapping_shapes.__and__(shielded_region) 

285 rdb_output(rdb_cat_nets, "Shielded Shapes", shielded_net_shapes) 

286 

287 unshielded_net_shapes = overlapping_shapes - shielded_net_shapes 

288 rdb_output(rdb_cat_nets, "Unshielded Shapes", unshielded_net_shapes) 

289 

290 area_um2 = overlapping_shapes.area() * dbu**2 

291 shielded_area_um2 = shielded_net_shapes.area() * dbu**2 

292 unshielded_area_um2 = area_um2 - shielded_area_um2 

293 cap_femto = unshielded_area_um2 * overlap_cap_spec.capacitance / 1000.0 

294 shielded_cap_femto = shielded_area_um2 * overlap_cap_spec.capacitance / 1000.0 

295 info(f"(Overlap): {top_layer_name}({net_top})-{bot_layer_name}({net_bot}): " 

296 f"Unshielded area: {unshielded_area_um2} µm^2, " 

297 f"cap: {round(cap_femto, 2)} fF") 

298 if cap_femto > 0.0: 

299 ovk = OverlapKey(layer_top=top_layer_name, 

300 net_top=net_top, 

301 layer_bot=bot_layer_name, 

302 net_bot=net_bot) 

303 cap = OverlapCap(key=ovk, 

304 cap_value=cap_femto, 

305 shielded_area=shielded_area_um2, 

306 unshielded_area=unshielded_area_um2, 

307 tech_spec=overlap_cap_spec) 

308 report.create_category( # used as info text 

309 rdb_cat_nets, 

310 f"{round(cap_femto, 3)} fF " 

311 f"({round(shielded_cap_femto, 3)} fF shielded " 

312 f"of total {round(cap_femto+shielded_cap_femto, 3)} fF)" 

313 ) 

314 extraction_results.overlap_coupling[ovk] = cap 

315 

316 # (2) SIDEWALL CAPACITANCE 

317 # 

318 for layer_name in layer2net2regions.keys(): 

319 if layer_name == substrate_layer_name: 

320 continue 

321 

322 sidewall_cap_spec = self.tech_info.sidewall_cap_by_layer_name.get(layer_name, None) 

323 if not sidewall_cap_spec: 

324 warning(f"No sidewall cap specified for layer {layer_name}") 

325 continue 

326 

327 net2regions = layer2net2regions.get(layer_name, None) 

328 if not net2regions: 

329 continue 

330 

331 rdb_cat_sw_layer = report.create_category(rdb_cat_sidewall_old, f"layer={layer_name}") 

332 

333 space_markers = space_markers_by_layer_name[layer_name] 

334 

335 for i, net1 in enumerate(net2regions.keys()): 

336 for j, net2 in enumerate(net2regions.keys()): 

337 if i < j: 

338 

339 # info(f"Sidewall on {layer_name}: Nets {net1} <-> {net2}") 

340 shapes1: kdb.Region = net2regions[net1] 

341 shapes2: kdb.Region = net2regions[net2] 

342 

343 markers_net1 = space_markers.interacting(shapes1) 

344 sidewall_edge_pairs = markers_net1.interacting(shapes2) 

345 

346 if not sidewall_edge_pairs: 

347 continue 

348 

349 rdb_cat_sw_nets = report.create_category(rdb_cat_sidewall_old, f"{net1} - {net2}") 

350 rdb_output(rdb_cat_sw_nets, f"Shapes {net1}", shapes1) 

351 rdb_output(rdb_cat_sw_nets, f"Shapes {net2}", shapes2) 

352 rdb_output(rdb_cat_sw_nets, f"Markers interacting {net1}", markers_net1) 

353 rdb_output(rdb_cat_sw_nets, f"Markers interacting {net1}-{net2}", sidewall_edge_pairs) 

354 

355 for idx, pair in enumerate(sidewall_edge_pairs): 

356 edge1: kdb.Edge = pair.first 

357 edge2: kdb.Edge = pair.second 

358 

359 # TODO: support non-parallel situations 

360 # avg_length = (edge1.length() + edge2.length()) / 2.0 

361 # avg_distance = (pair.polygon(0).perimeter() - edge1.length() - edge2.length()) / 2.0 

362 avg_length = min(edge1.length(), edge2.length()) 

363 avg_distance = pair.distance() 

364 

365 debug(f"Edge pair distance {avg_distance}, symmetric? {pair.symmetric}, " 

366 f"perimeter {pair.perimeter()}, parallel? {edge1.is_parallel(edge2)}") 

367 

368 # (3) SIDEWALL CAPACITANCE 

369 # 

370 # C = Csidewall * l * t / s 

371 # C = Csidewall * l / s 

372 

373 length_um = avg_length * dbu 

374 distance_um = avg_distance * dbu 

375 

376 # NOTE: this is automatically bidirectional, 

377 # whereas MAGIC counts 2 sidewall contributions (one for each side of the cap) 

378 cap_femto = (length_um * sidewall_cap_spec.capacitance) / \ 

379 (distance_um + sidewall_cap_spec.offset) / 1000.0 

380 

381 rdb_output(rdb_cat_sw_nets, f"Edge Pair {idx}: {round(cap_femto, 3)} fF", pair) 

382 

383 info(f"(Sidewall) layer {layer_name}: Nets {net1} <-> {net2}: {round(cap_femto, 5)} fF") 

384 

385 swk = SidewallKey(layer=layer_name, net1=net1, net2=net2) 

386 sw_cap = SidewallCap(key=swk, 

387 cap_value=cap_femto, 

388 distance=distance_um, 

389 length=length_um, 

390 tech_spec=sidewall_cap_spec) 

391 extraction_results.sidewall_table[swk] = sw_cap 

392 

393 # 

394 # (3) FRINGE / SIDE OVERLAP CAPACITANCE 

395 # 

396 

397 class FringeEdgeNeighborhoodVisitor(kdb.EdgeNeighborhoodVisitor): 

398 def __init__(self, 

399 inside_layer_name: str, 

400 inside_net_name: str, 

401 outside_layer_name: str, 

402 child_names: List[str], 

403 tech_info: TechInfo, 

404 report_category: rdb.RdbCategory): 

405 self.inside_layer_name = inside_layer_name 

406 self.inside_net_name = inside_net_name 

407 self.outside_layer_name = outside_layer_name 

408 self.child_names = child_names 

409 # NOTE: child_names[0] is the inside net (foreign) 

410 # child_names[1] is the shielded net (between layers) 

411 # child_names[2:] are the outside nets 

412 self.tech_info = tech_info 

413 self.report_category = report_category 

414 

415 # NOTE: overlap_cap_by_layer_names is top/bot (dict is not symmetric) 

416 self.overlap_cap_spec = tech_info.overlap_cap_by_layer_names[inside_layer_name].get(outside_layer_name, None) 

417 if not self.overlap_cap_spec: 

418 self.overlap_cap_spec = tech_info.overlap_cap_by_layer_names[outside_layer_name][inside_layer_name] 

419 

420 self.substrate_cap_spec = tech_info.substrate_cap_by_layer_name[inside_layer_name] 

421 self.sideoverlap_cap_spec = tech_info.side_overlap_cap_by_layer_names[inside_layer_name][outside_layer_name] 

422 

423 self.sidewall_cap_spec = tech_info.sidewall_cap_by_layer_name[inside_layer_name] 

424 

425 self.category_name_counter: Dict[str, int] = defaultdict(int) 

426 self.sidewall_counter = 0 

427 

428 def begin_polygon(self, 

429 layout: kdb.Layout, 

430 cell: kdb.Cell, 

431 polygon: kdb.Polygon): 

432 debug(f"----------------------------------------") 

433 debug(f"Polygon: {polygon}") 

434 

435 def end_polygon(self): 

436 debug(f"End of polygon") 

437 

438 def on_edge(self, 

439 layout: kdb.Layout, 

440 cell: kdb.Cell, 

441 edge: kdb.Edge, 

442 neighborhood: EdgeNeighborhood): 

443 # 

444 # NOTE: this complex operation will automatically rotate every edge to be on the x-axis 

445 # going from 0 to edge.length 

446 # so we only have to consider the y-axis to get the near and far distances 

447 # 

448 

449 # TODO: consider z-shielding! 

450 

451 debug(f"inside_layer={self.inside_layer_name}, " 

452 f"inside_net={self.inside_net_name}, " 

453 f"outside_layer={self.outside_layer_name}, " 

454 f"edge = {edge}") 

455 

456 rdb_inside_layer = report.create_category(rdb_cat_sidewall, f"layer={self.inside_layer_name}") 

457 rdb_sidewall_inside_net = report.create_category(rdb_inside_layer, f"inside={self.inside_net_name}") 

458 

459 for (x1, x2), polygons_by_net in neighborhood: 

460 if not polygons_by_net: 

461 continue 

462 

463 edge_interval_length = x2 - x1 

464 edge_interval_length_um = edge_interval_length * dbu 

465 

466 edge_interval_original = (self.to_original_trans(edge) * 

467 kdb.Edge(kdb.Point(x1, 0), kdb.Point(x2, 0))) 

468 transformed_category_name = f"Edge interval {(x1, x2)}" 

469 self.category_name_counter[transformed_category_name] += 1 

470 rdb_cat_edge_interval = report.create_category( 

471 self.report_category, 

472 f"{transformed_category_name} ({self.category_name_counter[transformed_category_name]})" 

473 ) 

474 rdb_output(rdb_cat_edge_interval, f"Original Section {edge_interval_original}", edge_interval_original) 

475 

476 polygons_on_same_layer = polygons_by_net.get(1, None) 

477 shielded_region_lateral = kdb.Region() 

478 if polygons_on_same_layer: 

479 shielded_region_lateral.insert(polygons_on_same_layer) 

480 rdb_output(rdb_cat_edge_interval, 'Laterally nearby shapes', 

481 kdb.Region([self.to_original_trans(edge) * p for p in shielded_region_lateral])) 

482 

483 # NOTE: first lateral nearby shape blocks everything beyond (like sidewall situation) up to halo 

484 def distance_near(p: kdb.Polygon) -> float: 

485 bbox: kdb.Box = p.bbox() 

486 

487 if not p.is_box(): 

488 warning(f"Side overlap, outside polygon {p} is not a box. " 

489 f"Currently, only boxes are supported, will be using bounding box {bbox}") 

490 ## distance_near = (bbox.p1.y + bbox.p2.y) / 2.0 

491 distance_near = min(bbox.p1.y, bbox.p2.y) 

492 if distance_near < 0: 

493 distance_near = 0 

494 return distance_near 

495 

496 nearest_lateral_shape = (math.inf, polygons_on_same_layer[0]) 

497 for p in polygons_on_same_layer: 

498 dnear = distance_near(p) 

499 if dnear < nearest_lateral_shape[0]: 

500 nearest_lateral_shape = (dnear, p) 

501 

502 rdb_output(rdb_cat_edge_interval, 'Closest nearby shape', 

503 kdb.Region(self.to_original_trans(edge) * nearest_lateral_shape[1])) 

504 

505 # NOTE: this method is always called for a single nearest edge (line), so the 

506 # polygons have 4 points. 

507 # Polygons points are sorted clockwise, so the edge 

508 # that goes from right-to-left is the nearest edge 

509 nearby_opposing_edge = [e for e in nearest_lateral_shape[1].each_edge() if e.d().x < 0][-1] 

510 nearby_opposing_edge_trans = self.to_original_trans(edge) * nearby_opposing_edge 

511 

512 opposing_net = '<unknown>' 

513 # find the opposing net 

514 for other_net, region in layer2net2regions[self.inside_layer_name].items(): 

515 if other_net == self.inside_net_name: 

516 continue 

517 if region.interacting(nearby_opposing_edge_trans).count() >= 1: 

518 # we found the other net! 

519 opposing_net = other_net 

520 break 

521 

522 rdb_output(rdb_cat_edge_interval, 

523 f"Closest nearby edge (net {opposing_net})", [nearby_opposing_edge_trans]) 

524 

525 sidewall_edge_pair = [nearby_opposing_edge_trans, edge_interval_original] 

526 distance_um = nearest_lateral_shape[0] * dbu 

527 sidewall_cap_femto = (edge_interval_length_um * self.sidewall_cap_spec.capacitance) / \ 

528 (distance_um + self.sidewall_cap_spec.offset) / 1000.0 / 2.0 

529 

530 rdb_sidewall_outside_net = report.create_category(rdb_sidewall_inside_net, 

531 f"outside={opposing_net}") 

532 self.sidewall_counter += 1 

533 rdb_output(rdb_sidewall_outside_net, 

534 f"#{self.sidewall_counter}: " 

535 f"len {round(edge_interval_length_um, 3)} µm, distance {round(distance_um, 3)} µm, " 

536 f"{round(sidewall_cap_femto, 3)} fF", 

537 sidewall_edge_pair) 

538 

539 nearby_shield = kdb.Polygon([nearby_opposing_edge.p1, 

540 nearby_opposing_edge.p2, 

541 kdb.Point(x1, side_halo_dbu), 

542 kdb.Point(x2, side_halo_dbu)]) 

543 

544 rdb_output(rdb_cat_edge_interval, 'Nearby shield', 

545 kdb.Region(self.to_original_trans(edge) * nearby_shield)) 

546 

547 shielded_region_between = kdb.Region() 

548 shielded_polygons = polygons_by_net.get(2, None) # shielded from layers between 

549 if shielded_polygons: 

550 shielded_region_between.insert(shielded_polygons) 

551 

552 for net_index, polygons in polygons_by_net.items(): 

553 if net_index == 0: # laterally shielded 

554 continue 

555 elif net_index == 1: # ignore "shielded" 

556 continue 

557 

558 if not polygons: 

559 continue 

560 

561 unshielded_region: kdb.Region = kdb.Region(polygons) - shielded_region_between 

562 if not unshielded_region: 

563 continue 

564 

565 net_name = self.child_names[net_index] 

566 rdb_cat_outside_net = report.create_category(rdb_cat_edge_interval, 

567 f"outside_net={net_name}") 

568 

569 rdb_output(rdb_cat_outside_net, 'Unshielded', 

570 kdb.Region([self.to_original_trans(edge) * p for p in unshielded_region])) 

571 

572 for p in unshielded_region: 

573 bbox: kdb.Box = p.bbox() 

574 

575 if not p.is_box(): 

576 warning(f"Side overlap, outside polygon {p} is not a box. " 

577 f"Currently, only boxes are supported, will be using bounding box {bbox}") 

578 distance_near = bbox.p1.y #+ 1 

579 if distance_near < 0: 

580 distance_near = 0 

581 distance_far = bbox.p2.y #- 2 

582 if distance_far < 0: 

583 distance_far = 0 

584 try: 

585 assert distance_near >= 0 

586 assert distance_far >= distance_near 

587 except AssertionError: 

588 print() 

589 raise 

590 

591 if distance_far == distance_near: 

592 continue 

593 

594 distance_near_um = distance_near * dbu 

595 distance_far_um = distance_far * dbu 

596 

597 # NOTE: overlap scaling is 1/50 (see MAGIC ExtTech) 

598 alpha_scale_factor = 0.02 * 0.01 * 0.5 * 200.0 

599 alpha_c = self.overlap_cap_spec.capacitance * alpha_scale_factor 

600 

601 # see Magic ExtCouple.c L1164 

602 cnear = (2.0 / math.pi) * math.atan(alpha_c * distance_near_um) 

603 cfar = (2.0 / math.pi) * math.atan(alpha_c * distance_far_um) 

604 

605 # "cfrac" is the fractional portion of the fringe cap seen 

606 # by tile tp along its length. This is independent of the 

607 # portion of the boundary length that tile tp occupies. 

608 cfrac = cfar - cnear 

609 

610 # The fringe portion extracted from the substrate will be 

611 # different than the portion added to the coupling layer. 

612 sfrac: float 

613 

614 # see Magic ExtCouple.c L1198 

615 alpha_s = self.substrate_cap_spec.area_capacitance / alpha_scale_factor 

616 if alpha_s != alpha_c: 

617 snear = (2.0 / math.pi) * math.atan(alpha_s * distance_near_um) 

618 sfar = (2.0 / math.pi) * math.atan(alpha_s * distance_far_um) 

619 sfrac = sfar - snear 

620 else: 

621 sfrac = cfrac 

622 

623 if outside_layer_name == substrate_layer_name: 

624 cfrac = sfrac 

625 

626 cap_femto = (cfrac * edge_interval_length_um * 

627 self.sideoverlap_cap_spec.capacitance / 1000.0) 

628 if cap_femto > 0.0: 

629 report.create_category(rdb_cat_outside_net, f"{round(cap_femto, 3)} fF") # used as info text 

630 

631 sok = SideOverlapKey(layer_inside=self.inside_layer_name, 

632 net_inside=self.inside_net_name, 

633 layer_outside=self.outside_layer_name, 

634 net_outside=net_name) 

635 sov = extraction_results.sideoverlap_table.get(sok, None) 

636 if sov: 

637 sov.cap_value += cap_femto 

638 else: 

639 sov = SideOverlapCap(key=sok, cap_value=cap_femto) 

640 extraction_results.sideoverlap_table[sok] = sov 

641 

642 # efflength = (cfrac - sov.so_coupfrac) * (double) length; 

643 # cap += e->ec_cap * efflength; 

644 # 

645 # subfrac += sov.so_subfrac; / *Just add the shielded fraction * / 

646 # efflength = (sfrac - subfrac) * (double) length; 

647 # 

648 # subcap = ExtCurStyle->exts_perimCap[ta][0] * efflength; 

649 

650 # TODO: shielding lateral 

651 

652 # TODO: fringe portion extracted from substrate 

653 

654 for inside_layer_name in layer2net2regions.keys(): 

655 if inside_layer_name == substrate_layer_name: 

656 continue 

657 

658 inside_net2regions = layer2net2regions.get(inside_layer_name, None) 

659 if not inside_net2regions: 

660 continue 

661 

662 inside_fringe_specs = self.tech_info.side_overlap_cap_by_layer_names.get(inside_layer_name, None) 

663 if not inside_fringe_specs: 

664 warning(f"No fringe / side overlap cap specified for layer inside={inside_layer_name}") 

665 continue 

666 

667 shapes_inside_layer = layer_regions_by_name[inside_layer_name] 

668 fringe_halo_inside = shapes_inside_layer.sized(side_halo_dbu) - shapes_inside_layer 

669 

670 rdb_cat_inside_layer = report.create_category(rdb_cat_fringe, f"inside_layer={inside_layer_name}") 

671 rdb_output(rdb_cat_inside_layer, "fringe_halo_inside", fringe_halo_inside) 

672 

673 # Side Overlap: metal <-> metal (additionally, substrate) 

674 for outside_layer_name in layer2net2regions.keys(): 

675 if inside_layer_name == outside_layer_name: 

676 continue 

677 

678 outside_net2regions = layer2net2regions.get(outside_layer_name, None) 

679 if not outside_net2regions: 

680 continue 

681 

682 cap_spec = inside_fringe_specs.get(outside_layer_name, None) 

683 if not cap_spec: 

684 warning(f"No side overlap cap specified for " 

685 f"layer inside={inside_layer_name}/outside={outside_layer_name}") 

686 continue 

687 

688 shapes_outside_layer = layer_regions_by_name[outside_layer_name] 

689 if not shapes_outside_layer: 

690 continue 

691 

692 shapes_outside_layer_within_halo = shapes_outside_layer.__and__(fringe_halo_inside) 

693 if not shapes_outside_layer_within_halo: 

694 continue 

695 

696 rdb_cat_outside_layer = report.create_category(rdb_cat_inside_layer, 

697 f"outside_layer={outside_layer_name}") 

698 

699 shielded_regions_between = shielded_regions_between_layers[(inside_layer_name, outside_layer_name)] 

700 rdb_output(rdb_cat_outside_layer, 'Shielded between layers', shielded_regions_between) 

701 

702 for net_inside in inside_net2regions.keys(): 

703 shapes_inside_net: kdb.Region = inside_net2regions[net_inside] 

704 if not shapes_inside_net: 

705 continue 

706 

707 rdb_cat_inside_net = report.create_category(rdb_cat_outside_layer, 

708 f"inside_net={net_inside}") 

709 

710 visitor = FringeEdgeNeighborhoodVisitor( 

711 inside_layer_name=inside_layer_name, 

712 inside_net_name=net_inside, 

713 outside_layer_name=outside_layer_name, 

714 child_names=[net_inside, 'NEARBY_SHAPES', 'SHIELD_BETWEEN'] + 

715 [k for k in outside_net2regions.keys() if k != net_inside], 

716 tech_info=self.tech_info, 

717 report_category=rdb_cat_inside_net 

718 ) 

719 

720 nearby_shapes = shapes_inside_layer - shapes_inside_net 

721 # children = [kdb.CompoundRegionOperationNode.new_secondary(shapes_inside_net), 

722 children = [kdb.CompoundRegionOperationNode.new_foreign(), 

723 kdb.CompoundRegionOperationNode.new_secondary(nearby_shapes), 

724 kdb.CompoundRegionOperationNode.new_secondary(shielded_regions_between)] + \ 

725 [kdb.CompoundRegionOperationNode.new_secondary(region) 

726 for net, region in list(outside_net2regions.items()) 

727 if net != net_inside] 

728 

729 node = kdb.CompoundRegionOperationNode.new_edge_neighborhood( 

730 children, 

731 visitor, 

732 0, # bext 

733 0, # eext, 

734 0, # din 

735 side_halo_dbu # dout 

736 ) 

737 

738 shapes_inside_net.complex_op(node) 

739 

740 for so in extraction_results.sideoverlap_table.values(): 

741 info(so) 

742 

743 return extraction_results