Coverage for klayout_pex/rcx25/c/overlap_extractor.py: 98%
64 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-02 23:32 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-02 23:32 +0000
1#! /usr/bin/env python3
2#
3# --------------------------------------------------------------------------------
4# SPDX-FileCopyrightText: 2024-2025 Martin Jan Köhler and Harald Pretl
5# Johannes Kepler University, Institute for Integrated Circuits.
6#
7# This file is part of KPEX
8# (see https://github.com/iic-jku/klayout-pex).
9#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program. If not, see <http://www.gnu.org/licenses/>.
22# SPDX-License-Identifier: GPL-3.0-or-later
23# --------------------------------------------------------------------------------
24#
26import klayout.db as kdb
28from klayout_pex.log import (
29 info,
30)
31from klayout_pex.tech_info import TechInfo
33from klayout_pex.rcx25.c.device_capacitor_plates import DeviceCapacitorPlates
34from klayout_pex.rcx25.types import PolygonNeighborhood
35from klayout_pex.rcx25.extraction_results import *
36from klayout_pex.rcx25.extraction_reporter import ExtractionReporter
39class OverlapExtractor:
40 def __init__(self,
41 all_layer_names: List[LayerName],
42 layer_regions_by_name: Dict[LayerName, kdb.Region],
43 dbu: float,
44 tech_info: TechInfo,
45 device_capacitor_plates: DeviceCapacitorPlates,
46 results: CellExtractionResults,
47 report: ExtractionReporter):
48 self.all_layer_names = all_layer_names
49 self.layer_regions_by_name = layer_regions_by_name
50 self.dbu = dbu
51 self.tech_info = tech_info
52 self.device_capacitor_plates = device_capacitor_plates
53 self.results = results
54 self.report = report
56 def extract(self):
57 for idx, (layer_name, layer_region) in enumerate(self.layer_regions_by_name.items()):
58 ovl_visitor = self.PEXPolygonNeighborhoodVisitor(
59 layer_names=self.all_layer_names,
60 inside_layer_index=idx,
61 dbu=self.dbu,
62 tech_info=self.tech_info,
63 device_capacitor_plates=self.device_capacitor_plates,
64 results=self.results,
65 report=self.report
66 )
68 # See comment above: as we use the layers in the stack order,
69 # the input index is also the metal layer index
70 ovl_children = [kdb.CompoundRegionOperationNode.new_secondary(r)
71 for r in self.layer_regions_by_name.values()]
73 # We don't use a distance - hence only true overlaps will be considered
74 ovl_node = kdb.CompoundRegionOperationNode.new_polygon_neighborhood(ovl_children, ovl_visitor)
76 layer_region.complex_op(ovl_node)
78 class PEXPolygonNeighborhoodVisitor(kdb.PolygonNeighborhoodVisitor):
79 def __init__(self,
80 layer_names: List[LayerName],
81 inside_layer_index: int,
82 dbu: float,
83 tech_info: TechInfo,
84 device_capacitor_plates: DeviceCapacitorPlates,
85 results: CellExtractionResults,
86 report: ExtractionReporter):
87 super().__init__()
88 self.layer_names = layer_names
89 self.inside_layer_index = inside_layer_index
90 self.dbu = dbu
91 self.tech_info = tech_info
92 self.device_capacitor_plates = device_capacitor_plates
93 self.results = results
94 self.report = report
96 def neighbors(self,
97 layout: kdb.Layout,
98 cell: kdb.Cell,
99 polygon: kdb.PolygonWithProperties,
100 neighborhood: PolygonNeighborhood):
101 # We just look "upwards", as we don't want to count areas twice
103 shielded_region = kdb.Region()
104 bottom_region = kdb.Region(polygon)
105 bot_layer_name = self.layer_names[self.inside_layer_index]
106 net_bot = polygon.property('net') # NOTE: the substrate's is the one of its well or VSUBS
108 for other_layer_index in range(self.inside_layer_index + 1, len(self.layer_names)):
109 polygons_above = neighborhood.get(other_layer_index, None)
110 if polygons_above is None:
111 continue
113 for polygon_above in polygons_above:
114 net_top = polygon_above.property('net')
116 if net_top == net_bot:
117 # NOTE: e.g. VDD over its nwell, which still shields the nwell from the shapes above
118 if bot_layer_name == self.tech_info.internal_substrate_layer_name:
119 shielded_region.insert(polygon_above)
120 continue
122 top_layer_name = self.layer_names[other_layer_index]
124 # NOTE: the capacitance of a device, which its model has (e.g. the junction of the diffusion
125 # and the substrate), but the shape above shields (e.g. the substrate from the metal above)
126 if self.tech_info.is_device_capacitance(top_layer_name, bot_layer_name):
127 shielded_region.insert(polygon_above)
128 continue
130 # NOTE: RCX25Extractor checked that the tech info has the overlap capacitance of each layer pair
131 overlap_cap_spec = self.tech_info.overlap_cap_by_layer_names[top_layer_name][bot_layer_name]
133 top_region = kdb.Region(polygon_above)
135 overlap_area = top_region.__and__(bottom_region) - shielded_region
137 overlap_area_um2 = overlap_area.area() * self.dbu ** 2
138 cap_femto = overlap_area_um2 * overlap_cap_spec.capacitance / 1000.0
139 # info(f"(Overlap): {top_layer_name}({net_top})-{bot_layer_name}({net_bot}): "
140 # f"cap: {round(cap_femto, 2)} fF, "
141 # f"area: {overlap_area_um2} µm^2")
143 # NOTE: the model of a device has the capacitance between its plates,
144 # but the plate above still shields the one below from the layers further above
145 if self.device_capacitor_plates.are_plates_of_a_device(polygon, net_bot, polygon_above, net_top):
146 cap_femto = 0.0
148 if cap_femto > 0.0:
149 ovk = OverlapKey(layer_top=top_layer_name,
150 net_top=net_top,
151 layer_bot=bot_layer_name,
152 net_bot=net_bot)
153 cap = OverlapCap(key=ovk,
154 cap_value=cap_femto,
155 shielded_area=0.0, # TODO shielded_area_um2,
156 unshielded_area=0.0, # TODO unshielded_area_um2,
157 tech_spec=overlap_cap_spec)
159 self.results.add_overlap_cap(cap)
161 self.report.output_overlap(overlap_cap=cap,
162 bottom_polygon=polygon,
163 top_polygon=polygon_above,
164 overlap_area=overlap_area)
166 shielded_region.insert(polygon_above)