Coverage for klayout_pex/rcx25/c/sidewall_and_fringe_extractor.py: 91%
187 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-03-31 19:36 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-03-31 19:36 +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#
26from functools import cached_property
27import math
29import klayout.db as kdb
31from klayout_pex.log import (
32 info,
33 warning
34)
35from klayout_pex.tech_info import TechInfo
37from klayout_pex.rcx25.c.geometry_restorer import GeometryRestorer
38from klayout_pex.rcx25.extraction_results import *
39from klayout_pex.rcx25.extraction_reporter import ExtractionReporter
40from klayout_pex.rcx25.c.polygon_utils import find_polygon_with_nearest_edge, nearest_edge
41from klayout_pex.rcx25.types import EdgeInterval, EdgeNeighborhood
42from process_parasitics_pb2 import CapacitanceInfo
45class SidewallAndFringeExtractor:
46 def __init__(self,
47 all_layer_names: List[LayerName],
48 layer_regions_by_name: Dict[LayerName, kdb.Region],
49 dbu: float,
50 scale_ratio_to_fit_halo: bool,
51 tech_info: TechInfo,
52 results: CellExtractionResults,
53 report: ExtractionReporter):
54 self.all_layer_names = all_layer_names
55 self.layer_regions_by_name = layer_regions_by_name
56 self.dbu = dbu
57 self.scale_ratio_to_fit_halo = scale_ratio_to_fit_halo
58 self.tech_info = tech_info
59 self.results = results
60 self.report = report
62 self.all_layer_regions = layer_regions_by_name.values()
64 def extract(self):
65 for idx, (layer_name, layer_region) in enumerate(self.layer_regions_by_name.items()):
66 other_layer_regions = [
67 r for ln, r in self.layer_regions_by_name.items()
68 if ln != layer_name
69 ]
71 en_visitor = self.PEXEdgeNeighborhoodVisitor(
72 all_layer_names=self.all_layer_names,
73 inside_layer_index=idx,
74 dbu=self.dbu,
75 scale_ratio_to_fit_halo=self.scale_ratio_to_fit_halo,
76 tech_info=self.tech_info,
77 results=self.results,
78 report=self.report
79 )
81 en_children = [kdb.CompoundRegionOperationNode.new_secondary(r)
82 for r in self.all_layer_regions]
83 en_children[idx] = kdb.CompoundRegionOperationNode.new_foreign() # sidewall of other nets on the same layer
84 en_children.append(kdb.CompoundRegionOperationNode.new_primary()) # opposing structures of the same polygon
86 side_halo_um = self.tech_info.tech.process_parasitics.side_halo
87 side_halo_dbu = int(side_halo_um / self.dbu) + 1 # add 1 nm to halo
89 en_node = kdb.CompoundRegionOperationNode.new_edge_neighborhood(
90 children=en_children,
91 visitor=en_visitor,
92 bext=-1, # NOTE: -1 dbu, suppresses quasi-empty contributions (will also suppress 90° edges)
93 eext=-1, # NOTE: -1 dbu, suppresses quasi-empty contributions (will also suppress 90° edges)
94 din=-1, # NOTE: -1 dbu, suppresses the edge itself appearing as a pseudo-polygon in new_primary()
95 dout=side_halo_dbu # dout
96 )
98 layer_region.complex_op(en_node)
100 # ------------------------------------------------------------------------
102 class PEXEdgeNeighborhoodVisitor(kdb.EdgeNeighborhoodVisitor):
103 def __init__(self,
104 all_layer_names: List[LayerName],
105 inside_layer_index: int,
106 dbu: float,
107 tech_info: TechInfo,
108 scale_ratio_to_fit_halo: bool,
109 results: CellExtractionResults,
110 report: ExtractionReporter):
111 super().__init__()
113 self.all_layer_names = all_layer_names
114 self.inside_layer_index = inside_layer_index
115 self.dbu = dbu
116 self.tech_info = tech_info
117 self.scale_ratio_to_fit_halo = scale_ratio_to_fit_halo
118 self.results = results
119 self.report = report
121 # NOTE: prepare layers below and layers above the "inside" layer,
122 # each prepared for iteration that allows iterativly growing a shield region
123 self.layer_below_indices = reversed(range(0, inside_layer_index))
124 self.layer_above_indices = range(inside_layer_index,
125 len(all_layer_names) - inside_layer_index)
127 @cached_property
128 def inside_layer_name(self) -> LayerName:
129 return self.all_layer_names[self.inside_layer_index]
131 def begin_polygon(self,
132 layout: kdb.Layout,
133 cell: kdb.Cell,
134 polygon: kdb.Polygon):
135 pass
137 def end_polygon(self):
138 pass
140 @cached_property
141 def side_halo(self) -> float:
142 return self.tech_info.tech.process_parasitics.side_halo
144 def on_edge(self,
145 layout: kdb.Layout,
146 cell: kdb.Cell,
147 edge: kdb.EdgeWithProperties,
148 neighborhood: EdgeNeighborhood):
149 #
150 # NOTE: this complex operation will automatically rotate every edge to be on the x-axis
151 # going from 0 to edge.length
152 # so we only have to consider the y-axis to get the near and far distances
153 #
154 geometry_restorer = GeometryRestorer(self.to_original_trans(edge))
156 self.report.output_edge_neighborhood(inside_layer=self.inside_layer_name,
157 all_layer_names=self.all_layer_names,
158 edge=edge,
159 neighborhood=neighborhood,
160 geometry_restorer=geometry_restorer)
162 for edge_interval, polygons_by_child in neighborhood:
163 if not polygons_by_child:
164 continue
166 edge_interval_length = edge_interval[1] - edge_interval[0]
167 if edge_interval_length <= 1:
168 warning(f"Short edge interval {edge_interval} "
169 f"(length {edge_interval_length * self.dbu * 1000} nm), "
170 f"expected to be dropped due to bext/eext parameters, skipping…")
171 continue
173 layer_fringe_shields = [kdb.Region() for _ in self.all_layer_names]
174 for child_index, polygons in polygons_by_child.items():
175 if child_index < len(self.all_layer_names):
176 layer_fringe_shields[child_index].insert(polygons)
178 # NOTE: lateral fringe shielding, can be caused by
179 # - sidewall (other net)
180 # - same net "sidewall" (other polygons)
181 # - even opposing edges of the same polygon of the same net!
182 # fringe to shapes on other layers will be limited by this distance
183 # (i.e., fringe is shielded beyond this distance)
185 nearest_distance: Optional[float] = None
186 nearest_lateral_edge: Optional[kdb.EdgeWithProperties] = None
188 for child_index, polygons in polygons_by_child.items():
189 if child_index == len(self.all_layer_names): # TODO, fix index, same layer, same polygon
190 distance, nearby_polygon = find_polygon_with_nearest_edge(polygons_on_same_layer=polygons)
192 if nearest_distance is None or \
193 distance < nearest_distance:
194 nearest_distance = distance
195 nearest_lateral_edge = nearest_edge(nearby_polygon)
196 elif self.inside_layer_index == child_index: # SIDEWALL!
197 # NOTE: use only the nearest polygon,
198 # as the others are laterally shielded by the nearer ones
199 distance, nearby_polygon = find_polygon_with_nearest_edge(polygons_on_same_layer=polygons)
201 if nearest_distance is None or \
202 distance < nearest_distance:
203 nearest_distance = distance
204 nearest_lateral_edge = nearest_edge(nearby_polygon)
206 self.emit_sidewall(
207 layer_name=self.inside_layer_name,
208 edge=edge,
209 edge_interval=edge_interval,
210 polygon=nearby_polygon,
211 geometry_restorer=geometry_restorer
212 )
214 lateral_shield: Optional[kdb.Polygon] = None
215 if nearest_lateral_edge is not None:
216 lateral_shield = kdb.Polygon([
217 nearest_lateral_edge.p2,
218 nearest_lateral_edge.p1,
219 kdb.Point(nearest_lateral_edge.p1.x, (self.side_halo + 10) / self.dbu),
220 kdb.Point(nearest_lateral_edge.p2.x, (self.side_halo + 10) / self.dbu),
221 ])
223 for child_index, polygons in polygons_by_child.items():
224 if self.inside_layer_index == child_index:
225 continue # already handled above
226 elif child_index < len(self.all_layer_names): # FRINGE!
227 fringe_shield = kdb.Region()
228 if lateral_shield is not None:
229 fringe_shield.insert(lateral_shield)
230 if child_index < self.inside_layer_index:
231 r = range(child_index + 1, self.inside_layer_index)
232 for idx in r:
233 fringe_shield += layer_fringe_shields[idx]
234 elif self.inside_layer_index < child_index:
235 r = range(self.inside_layer_index + 1, child_index)
236 for idx in r:
237 fringe_shield += layer_fringe_shields[idx]
239 # NOTE:
240 # polygons can have different nets
241 # polygons can be segmented after shield is applied
243 self.emit_fringe(
244 inside_layer_name=self.inside_layer_name,
245 outside_layer_name=self.all_layer_names[child_index],
246 edge=edge,
247 edge_interval=edge_interval,
248 outside_polygons=polygons,
249 shield=fringe_shield,
250 lateral_shield=lateral_shield,
251 geometry_restorer=geometry_restorer)
253 def emit_sidewall(self,
254 layer_name: LayerName,
255 edge: kdb.EdgeWithProperties,
256 edge_interval: EdgeInterval,
257 polygon: kdb.PolygonWithProperties,
258 geometry_restorer: GeometryRestorer):
259 net1 = edge.property('net')
260 net2 = polygon.property('net')
262 if net1 == net2:
263 return
265 sidewall_cap_spec = self.tech_info.sidewall_cap_by_layer_name[layer_name]
267 # TODO!
269 # NOTE: this method is always called for a single nearest edge (line), so the
270 # polygons have 4 points.
271 # Polygons points are sorted clockwise, so the edge
272 # that goes from right-to-left is the nearest edge
273 # nearby_opposing_edge = [e for e in nearest_lateral_shape[1].each_edge() if e.d().x < 0][-1]
274 # nearby_opposing_edge_trans = geometry_restorer.restore_edge(edge) * nearby_opposing_edge
276 # C = Csidewall * l * t / s
277 # C = Csidewall * l / s
279 avg_length = edge_interval[1] - edge_interval[0]
280 avg_distance = min(polygon.bbox().p1.y, polygon.bbox().p2.y)
282 outside_edge = nearest_edge(polygon)
284 length_um = avg_length * self.dbu
285 distance_um = avg_distance * self.dbu
287 # NOTE: dividing by 2 (like MAGIC this not bidirectional),
288 # but we count 2 sidewall contributions (one for each side of the cap)
289 cap_femto = ((length_um * sidewall_cap_spec.capacitance)
290 / (distance_um + sidewall_cap_spec.offset)
291 / 2.0 # non-bidirectional (half)
292 / 1000.0) # aF -> fF
294 info(f"(Sidewall) layer {layer_name}: Nets {net1} <-> {net2}: {round(cap_femto, 5)} fF")
296 swk = SidewallKey(layer=layer_name, net1=net1, net2=net2)
297 sw_cap = SidewallCap(key=swk,
298 cap_value=cap_femto,
299 distance=distance_um,
300 length=length_um,
301 tech_spec=sidewall_cap_spec)
302 self.results.add_sidewall_cap(sw_cap)
304 self.report.output_sidewall(
305 sidewall_cap=sw_cap,
306 inside_edge=geometry_restorer.restore_edge_interval(edge_interval),
307 outside_edge=geometry_restorer.restore_edge(outside_edge)
308 )
310 def fringe_cap(self,
311 edge_interval_length: float,
312 distance_near: float,
313 distance_far: float,
314 overlap_cap_spec: CapacitanceInfo.OverlapCapacitance,
315 sideoverlap_cap_spec: CapacitanceInfo.SideOverlapCapacitance) -> float:
316 distance_near_um = distance_near * self.dbu
317 distance_far_um = distance_far * self.dbu
318 edge_interval_length_um = edge_interval_length * self.dbu
320 # NOTE: overlap scaling is 1/50 (see MAGIC ExtTech)
321 alpha_scale_factor = 0.02 * 0.01 * 0.5 * 200.0
322 alpha_c = overlap_cap_spec.capacitance * alpha_scale_factor
324 # see Magic ExtCouple.c L1164
325 cnear = (2.0 / math.pi) * math.atan(alpha_c * distance_near_um)
326 cfar = (2.0 / math.pi) * math.atan(alpha_c * distance_far_um)
328 if self.scale_ratio_to_fit_halo:
329 full_halo_ratio = (2.0 / math.pi) * math.atan(alpha_c * self.side_halo)
330 # NOTE: for a large enough halo, full_halo would be 1,
331 # but it is smaller, so we compensate
332 if full_halo_ratio < 1.0:
333 cnear /= full_halo_ratio
334 cfar /= full_halo_ratio
336 # "cfrac" is the fractional portion of the fringe cap seen
337 # by tile tp along its length. This is independent of the
338 # portion of the boundary length that tile tp occupies.
339 cfrac = cfar - cnear
341 cap_femto = (cfrac * edge_interval_length_um *
342 sideoverlap_cap_spec.capacitance / 1000.0)
344 return cap_femto
346 def emit_fringe(self,
347 inside_layer_name: LayerName,
348 outside_layer_name: LayerName,
349 edge: kdb.EdgeWithProperties,
350 edge_interval: EdgeInterval,
351 outside_polygons: List[kdb.PolygonWithProperties],
352 shield: kdb.Region,
353 lateral_shield: kdb.Polygon,
354 geometry_restorer: GeometryRestorer):
355 inside_net_name = self.tech_info.internal_substrate_layer_name \
356 if inside_layer_name == self.tech_info.internal_substrate_layer_name \
357 else edge.property('net')
359 # NOTE: each polygon in outside_polygons
360 # - could have a different net
361 # - could be segmented by a shield into multiple polygons
362 # each with different near/far regions
364 outside_net_names = [
365 self.tech_info.internal_substrate_layer_name \
366 if outside_layer_name == self.tech_info.internal_substrate_layer_name \
367 else p.property('net')
368 for p in outside_polygons
369 ]
371 same_net_markers = [
372 inside_net_name == outside_net_name
373 for outside_net_name in outside_net_names
374 ]
376 # NOTE: overlap_cap_by_layer_names is top/bot (dict is not symmetric)
377 overlap_cap_spec = self.tech_info.overlap_cap_by_layer_names[inside_layer_name].get(outside_layer_name,
378 None)
379 if not overlap_cap_spec:
380 overlap_cap_spec = self.tech_info.overlap_cap_by_layer_names[outside_layer_name][inside_layer_name]
382 substrate_cap_spec = self.tech_info.substrate_cap_by_layer_name[inside_layer_name]
383 sideoverlap_cap_spec = self.tech_info.side_overlap_cap_by_layer_names[inside_layer_name][
384 outside_layer_name]
386 polygons_by_net: Dict[NetName, List[kdb.PolygonWithProperties]] = defaultdict(list)
388 for idx, p in enumerate(outside_polygons):
389 outside_net = outside_net_names[idx]
390 is_same_net = same_net_markers[idx]
392 if is_same_net:
393 # TODO: log?
394 continue
396 if shield.is_empty():
397 polygons_by_net[outside_net].append(p)
398 else:
399 unshielded_region = kdb.Region(p)
400 unshielded_region.enable_properties()
401 unshielded_region -= shield
402 if unshielded_region.is_empty():
403 # TODO: log?
404 continue
406 for up in unshielded_region.each():
407 up = kdb.PolygonWithProperties(up, {'net': outside_net})
408 polygons_by_net[outside_net].append(up)
409 # if p != up:
410 # print(f"Unshieleded polygon {up}, differs from original polygon {p}")
412 for outside_net_name, polygons in polygons_by_net.items():
413 for p in polygons:
414 bbox = p.bbox()
415 if not p.is_box():
416 warning(f"Side overlap, polygon {p} is not a box. "
417 f"Currently, only boxes are supported, will be using bounding box {bbox}")
419 distance_near = bbox.p1.y # + 1
420 if distance_near < 0:
421 distance_near = 0
422 distance_far = bbox.p2.y # - 2
423 if distance_far < 0:
424 distance_far = 0
425 try:
426 assert distance_near >= 0
427 assert distance_far >= distance_near
428 except AssertionError:
429 print()
430 raise
432 if distance_far == distance_near:
433 return
435 edge_interval_length = edge_interval[1] - edge_interval[0]
436 edge_interval_length_um = edge_interval_length * self.dbu
438 cap_femto = self.fringe_cap(edge_interval_length=edge_interval_length,
439 distance_near=distance_near,
440 distance_far=distance_far,
441 overlap_cap_spec=overlap_cap_spec,
442 sideoverlap_cap_spec=sideoverlap_cap_spec)
444 if cap_femto > 0.0001: # TODO: configurable threshold, but keeping accumulation might also be nice
445 info(f"(Side Overlap) "
446 f"{inside_layer_name}({inside_net_name})-{outside_layer_name}({outside_net_name}): "
447 f"{round(cap_femto, 5)} fF, "
448 f"edge interval length = {round(edge_interval_length_um, 2)} µm")
450 sok = SideOverlapKey(layer_inside=inside_layer_name,
451 net_inside=inside_net_name,
452 layer_outside=outside_layer_name,
453 net_outside=outside_net_name)
454 soc = SideOverlapCap(key=sok, cap_value=cap_femto)
455 self.results.add_sideoverlap_cap(soc)
457 self.report.output_sideoverlap(
458 sideoverlap_cap=soc,
459 inside_edge=geometry_restorer.restore_edge_interval(edge_interval),
460 outside_polygon=geometry_restorer.restore_polygon(p),
461 lateral_shield=geometry_restorer.restore_polygon(lateral_shield) \
462 if lateral_shield is not None else None
463 )