Coverage for klayout_pex/rcx25/extractor.py: 21%
103 statements
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-08 18:54 +0000
« prev ^ index » next coverage.py v7.10.2, created at 2025-08-08 18:54 +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/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 klayout.db as kdb
28from ..klayout.lvsdb_extractor import KLayoutExtractionContext, GDSPair
29from ..log import (
30 debug,
31 warning,
32 error,
33 info,
34 subproc,
35 rule
36)
37from ..tech_info import TechInfo
38from .extraction_results import *
39from .extraction_reporter import ExtractionReporter
40from .pex_mode import PEXMode
41from klayout_pex.rcx25.c.overlap_extractor import OverlapExtractor
42from klayout_pex.rcx25.c.sidewall_and_fringe_extractor import SidewallAndFringeExtractor
43from klayout_pex.rcx25.r.r_extractor import RExtractor
45import klayout_pex_protobuf.kpex.geometry.shapes_pb2 as shapes_pb2
46import klayout_pex_protobuf.kpex.layout.location_pb2 as location_pb2
47import klayout_pex_protobuf.kpex.request.pex_request_pb2 as pex_request_pb2
48import klayout_pex_protobuf.kpex.result.pex_result_pb2 as pex_result_pb2
49import klayout_pex_protobuf.kpex.klayout.r_extractor_tech_pb2 as rex_tech_pb2
50from klayout_pex_protobuf.kpex.klayout.r_extractor_tech_pb2 import RExtractorTech as pb_RExtractorTech
53class RCX25Extractor:
54 def __init__(self,
55 pex_context: KLayoutExtractionContext,
56 pex_mode: PEXMode,
57 scale_ratio_to_fit_halo: bool,
58 delaunay_amax: float,
59 delaunay_b: float,
60 tech_info: TechInfo,
61 report_path: str):
62 self.pex_context = pex_context
63 self.pex_mode = pex_mode
64 self.scale_ratio_to_fit_halo = scale_ratio_to_fit_halo
65 self.delaunay_amax = delaunay_amax
66 self.delaunay_b = delaunay_b
67 self.tech_info = tech_info
68 self.report_path = report_path
70 if "PolygonWithProperties" not in kdb.__all__:
71 raise Exception("KLayout version does not support properties (needs 0.30 at least)")
73 # TODO: remove this function by inlining
74 def gds_pair(self, layer_name) -> Optional[GDSPair]:
75 return self.tech_info.gds_pair(layer_name)
77 def shapes_of_layer(self, layer_name: str) -> Optional[kdb.Region]:
78 gds_pair = self.gds_pair(layer_name=layer_name)
79 if not gds_pair:
80 return None
82 shapes = self.pex_context.shapes_of_layer(gds_pair=gds_pair)
83 if not shapes:
84 debug(f"Nothing extracted for layer {layer_name}")
86 return shapes
88 def extract(self) -> ExtractionResults:
89 extraction_results = ExtractionResults()
91 # TODO: for now, we always flatten and have only 1 cell
92 cell_name = self.pex_context.annotated_top_cell.name
93 extraction_report = ExtractionReporter(cell_name=cell_name,
94 dbu=self.pex_context.dbu)
95 cell_extraction_results = CellExtractionResults(cell_name=cell_name)
97 # Explicitly log the stacktrace here, because otherwise Exceptions
98 # raised in the callbacks of *NeighborhoodVisitors can cause RuntimeErrors
99 # that are not traceable beyond the Region.complex_op() calls
100 try:
101 self.extract_cell(results=cell_extraction_results,
102 report=extraction_report)
103 except RuntimeError as e:
104 import traceback
105 print(f"Caught a RuntimeError: {e}")
106 traceback.print_exc()
107 raise
109 extraction_results.cell_extraction_results[cell_name] = cell_extraction_results
111 extraction_report.save(self.report_path)
113 return extraction_results
115 def extract_cell(self,
116 results: CellExtractionResults,
117 report: ExtractionReporter):
118 netlist: kdb.Netlist = self.pex_context.lvsdb.netlist()
119 dbu = self.pex_context.dbu
120 # ------------------------------------------------------------------------
122 layer_regions_by_name: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
124 all_region = kdb.Region()
125 all_region.enable_properties()
127 substrate_region = kdb.Region()
128 substrate_region.enable_properties()
130 side_halo_um = self.tech_info.tech.process_parasitics.side_halo
131 substrate_region.insert(self.pex_context.top_cell_bbox().enlarged(side_halo_um / dbu)) # e.g. 8 µm halo
133 layer_regions_by_name[self.tech_info.internal_substrate_layer_name] = substrate_region
135 via_name_below_layer_name: Dict[LayerName, Optional[LayerName]] = {}
136 via_name_above_layer_name: Dict[LayerName, Optional[LayerName]] = {}
137 via_regions_by_via_name: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region)
139 previous_via_name: Optional[str] = None
141 for metal_layer in self.tech_info.process_metal_layers:
142 layer_name = metal_layer.name
143 gds_pair = self.gds_pair(layer_name)
144 canonical_layer_name = self.tech_info.canonical_layer_name_by_gds_pair[gds_pair]
146 all_layer_shapes = self.shapes_of_layer(layer_name)
147 if all_layer_shapes is not None:
148 all_layer_shapes.enable_properties()
150 layer_regions_by_name[canonical_layer_name] += all_layer_shapes
151 layer_regions_by_name[canonical_layer_name].enable_properties()
152 all_region += all_layer_shapes
154 if metal_layer.metal_layer.HasField('contact_above'):
155 contact = metal_layer.metal_layer.contact_above
157 via_regions = self.shapes_of_layer(contact.name)
158 if via_regions is not None:
159 via_regions.enable_properties()
160 via_regions_by_via_name[contact.name] += via_regions
161 via_name_above_layer_name[canonical_layer_name] = contact.name
162 via_name_below_layer_name[canonical_layer_name] = previous_via_name
164 previous_via_name = contact.name
165 else:
166 previous_via_name = None
168 all_layer_names = list(layer_regions_by_name.keys())
170 # ------------------------------------------------------------------------
171 if self.pex_mode.need_capacitance():
172 overlap_extractor = OverlapExtractor(
173 all_layer_names=all_layer_names,
174 layer_regions_by_name=layer_regions_by_name,
175 dbu=dbu,
176 tech_info=self.tech_info,
177 results=results,
178 report=report
179 )
180 overlap_extractor.extract()
182 sidewall_and_fringe_extractor = SidewallAndFringeExtractor(
183 all_layer_names=all_layer_names,
184 layer_regions_by_name=layer_regions_by_name,
185 dbu=dbu,
186 scale_ratio_to_fit_halo=self.scale_ratio_to_fit_halo,
187 tech_info=self.tech_info,
188 results=results,
189 report=report
190 )
191 sidewall_and_fringe_extractor.extract()
193 # ------------------------------------------------------------------------
194 if self.pex_mode.need_resistance():
195 c: kdb.Circuit = netlist.top_circuit()
196 info(f"LVSDB: found {c.pin_count()}pins")
198 # FIXME:
199 # currenly, tesselation does not work:
200 # https://github.com/KLayout/klayout/issues/2100
201 r_extractor = RExtractor(pex_context=self.pex_context,
202 substrate_algorithm=pb_RExtractorTech.Algorithm.ALGORITHM_SQUARE_COUNTING,
203 #substrate_algorithm = pb_RExtractorTech.Algorithm.ALGORITHM_TESSELATION,
204 wire_algorithm = pb_RExtractorTech.Algorithm.ALGORITHM_SQUARE_COUNTING,
205 delaunay_b = self.delaunay_b,
206 delaunay_amax = self.delaunay_amax,
207 via_merge_distance = 0,
208 skip_simplify = True)
209 rex_request = r_extractor.prepare_request()
210 report.output_rex_request(request=rex_request)
212 rex_result = r_extractor.extract(rex_request)
213 report.output_rex_result(result=rex_result)
215 #
216 # node_by_id: Dict[int, r_network_pb2.RNode] = {}
217 # subproc("\tNodes:")
218 # for node in rex_result.nodes:
219 # node_by_id[node.node_id] = node
220 #
221 # msg = f"\t\tNode #{hex(node.node_id)} '{node.node_name}' " \
222 # f"of net '{node.net_name}' " \
223 # f"on layer '{node.layer_name}' "
224 # match node.location.kind:
225 # case location_pb2.Location.Kind.LOCATION_KIND_POINT:
226 # p = node.location.point
227 # msg += f"at {p.x},{p.y} ({p.x * dbu} µm, {p.y * dbu} µm)"
228 # case location_pb2.Location.Kind.LOCATION_KIND_BOX:
229 # b = node.location.box
230 # msg += f"at {b.lower_left.x},{b.lower_left.y};{b.upper_right.x},{b.upper_right.y} (" \
231 # f"B/L {round(b.lower_left.x * dbu, 3)},"\
232 # f"{round(b.lower_left.y * dbu, 3)} µm, " \
233 # f"T/R {round(b.upper_right.x * dbu, 3)},"\
234 # f"{round(b.upper_right.y * dbu)} µm)"
235 # subproc(msg)
236 #
237 # subproc("\tElements:")
238 # for element in rex_result.elements:
239 # node_a = node_by_id[element.node_a.node_id]
240 # node_b = node_by_id[element.node_b.node_id]
241 # subproc(f"\t\t{node_a.node_name} (port net '{node_a.net_name}') "
242 # f"↔︎ {node_b.node_name} (port net '{node_b.net_name}') "
243 # f"{round(element.resistance, 3)} Ω")
245 results.r_extraction_result = rex_result
247 return results