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
« 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#
26import math
27from collections import defaultdict
28from typing import *
30import klayout.db as kdb
31import klayout.rdb as rdb
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
46EdgeInterval = Tuple[float, float]
47ChildIndex = int
48EdgeNeighborhood = List[Tuple[EdgeInterval, Dict[ChildIndex, List[kdb.Polygon]]]]
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
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
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
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
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
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
89 def extract(self) -> ExtractionResults:
90 extraction_results = ExtractionResults()
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
98 report.save(self.report_path)
100 return extraction_results
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
109 extraction_results = CellExtractionResults(cell_name=cell_name)
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")
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)
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}")
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] = {}
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
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
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]
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
181 previous_layer_name = canonical_layer_name
183 for net in circuit.each_net():
184 net_name = net.expanded_name()
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
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)
206 #----------------------------------------------------------------------------------------
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
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")
214 for layer_name in layer2net2regions.keys():
215 if layer_name == substrate_layer_name:
216 continue
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)
235 #
236 # (1) OVERLAP CAPACITANCE
237 #
238 for top_layer_name in layer2net2regions.keys():
239 if top_layer_name == substrate_layer_name:
240 continue
242 top_net2regions = layer2net2regions.get(top_layer_name, None)
243 if not top_net2regions:
244 continue
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
251 rdb_cat_top_layer = report.create_category(rdb_cat_overlap, f"top_layer={top_layer_name}")
253 shapes_top_layer = layer_regions_by_name[top_layer_name]
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
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
265 rdb_cat_bot_layer = report.create_category(rdb_cat_top_layer, f"bot_layer={bot_layer_name}")
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)
270 for net_top in top_net2regions.keys():
271 shapes_top_net: kdb.Region = top_net2regions[net_top].dup()
273 for net_bot in bot_net2regions.keys():
274 if net_top == net_bot:
275 continue
277 shapes_bot_net: kdb.Region = bot_net2regions[net_bot]
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)
284 shielded_net_shapes = overlapping_shapes.__and__(shielded_region)
285 rdb_output(rdb_cat_nets, "Shielded Shapes", shielded_net_shapes)
287 unshielded_net_shapes = overlapping_shapes - shielded_net_shapes
288 rdb_output(rdb_cat_nets, "Unshielded Shapes", unshielded_net_shapes)
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
316 # (2) SIDEWALL CAPACITANCE
317 #
318 for layer_name in layer2net2regions.keys():
319 if layer_name == substrate_layer_name:
320 continue
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
327 net2regions = layer2net2regions.get(layer_name, None)
328 if not net2regions:
329 continue
331 rdb_cat_sw_layer = report.create_category(rdb_cat_sidewall_old, f"layer={layer_name}")
333 space_markers = space_markers_by_layer_name[layer_name]
335 for i, net1 in enumerate(net2regions.keys()):
336 for j, net2 in enumerate(net2regions.keys()):
337 if i < j:
339 # info(f"Sidewall on {layer_name}: Nets {net1} <-> {net2}")
340 shapes1: kdb.Region = net2regions[net1]
341 shapes2: kdb.Region = net2regions[net2]
343 markers_net1 = space_markers.interacting(shapes1)
344 sidewall_edge_pairs = markers_net1.interacting(shapes2)
346 if not sidewall_edge_pairs:
347 continue
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)
355 for idx, pair in enumerate(sidewall_edge_pairs):
356 edge1: kdb.Edge = pair.first
357 edge2: kdb.Edge = pair.second
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()
365 debug(f"Edge pair distance {avg_distance}, symmetric? {pair.symmetric}, "
366 f"perimeter {pair.perimeter()}, parallel? {edge1.is_parallel(edge2)}")
368 # (3) SIDEWALL CAPACITANCE
369 #
370 # C = Csidewall * l * t / s
371 # C = Csidewall * l / s
373 length_um = avg_length * dbu
374 distance_um = avg_distance * dbu
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
381 rdb_output(rdb_cat_sw_nets, f"Edge Pair {idx}: {round(cap_femto, 3)} fF", pair)
383 info(f"(Sidewall) layer {layer_name}: Nets {net1} <-> {net2}: {round(cap_femto, 5)} fF")
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
393 #
394 # (3) FRINGE / SIDE OVERLAP CAPACITANCE
395 #
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
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]
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]
423 self.sidewall_cap_spec = tech_info.sidewall_cap_by_layer_name[inside_layer_name]
425 self.category_name_counter: Dict[str, int] = defaultdict(int)
426 self.sidewall_counter = 0
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}")
435 def end_polygon(self):
436 debug(f"End of polygon")
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 #
449 # TODO: consider z-shielding!
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}")
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}")
459 for (x1, x2), polygons_by_net in neighborhood:
460 if not polygons_by_net:
461 continue
463 edge_interval_length = x2 - x1
464 edge_interval_length_um = edge_interval_length * dbu
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)
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]))
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()
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
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)
502 rdb_output(rdb_cat_edge_interval, 'Closest nearby shape',
503 kdb.Region(self.to_original_trans(edge) * nearest_lateral_shape[1]))
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
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
522 rdb_output(rdb_cat_edge_interval,
523 f"Closest nearby edge (net {opposing_net})", [nearby_opposing_edge_trans])
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
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)
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)])
544 rdb_output(rdb_cat_edge_interval, 'Nearby shield',
545 kdb.Region(self.to_original_trans(edge) * nearby_shield))
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)
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
558 if not polygons:
559 continue
561 unshielded_region: kdb.Region = kdb.Region(polygons) - shielded_region_between
562 if not unshielded_region:
563 continue
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}")
569 rdb_output(rdb_cat_outside_net, 'Unshielded',
570 kdb.Region([self.to_original_trans(edge) * p for p in unshielded_region]))
572 for p in unshielded_region:
573 bbox: kdb.Box = p.bbox()
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
591 if distance_far == distance_near:
592 continue
594 distance_near_um = distance_near * dbu
595 distance_far_um = distance_far * dbu
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
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)
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
610 # The fringe portion extracted from the substrate will be
611 # different than the portion added to the coupling layer.
612 sfrac: float
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
623 if outside_layer_name == substrate_layer_name:
624 cfrac = sfrac
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
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
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;
650 # TODO: shielding lateral
652 # TODO: fringe portion extracted from substrate
654 for inside_layer_name in layer2net2regions.keys():
655 if inside_layer_name == substrate_layer_name:
656 continue
658 inside_net2regions = layer2net2regions.get(inside_layer_name, None)
659 if not inside_net2regions:
660 continue
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
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
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)
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
678 outside_net2regions = layer2net2regions.get(outside_layer_name, None)
679 if not outside_net2regions:
680 continue
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
688 shapes_outside_layer = layer_regions_by_name[outside_layer_name]
689 if not shapes_outside_layer:
690 continue
692 shapes_outside_layer_within_halo = shapes_outside_layer.__and__(fringe_halo_inside)
693 if not shapes_outside_layer_within_halo:
694 continue
696 rdb_cat_outside_layer = report.create_category(rdb_cat_inside_layer,
697 f"outside_layer={outside_layer_name}")
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)
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
707 rdb_cat_inside_net = report.create_category(rdb_cat_outside_layer,
708 f"inside_net={net_inside}")
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 )
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]
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 )
738 shapes_inside_net.complex_op(node)
740 for so in extraction_results.sideoverlap_table.values():
741 info(so)
743 return extraction_results