Coverage for klayout_pex/rcx25/extraction_reporter.py: 78%
106 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#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of KPEX
7# (see https://github.com/martinjankoehler/klayout-pex).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
25from functools import cached_property
27import klayout.rdb as rdb
28import klayout.db as kdb
30from .extraction_results import *
31from klayout_pex.rcx25.c.geometry_restorer import GeometryRestorer
32from .types import EdgeNeighborhood, LayerName
33from ..klayout.lvsdb_extractor import KLayoutDeviceInfo
35VarShapes = kdb.Shapes | kdb.Region | List[kdb.Edge] | List[kdb.Polygon]
38class ExtractionReporter:
39 def __init__(self,
40 cell_name: str,
41 dbu: float):
42 self.report = rdb.ReportDatabase(f"PEX {cell_name}")
43 self.cell = self.report.create_cell(cell_name)
44 self.dbu_trans = kdb.CplxTrans(mag=dbu)
45 self.category_name_counter: Dict[str, int] = defaultdict(int)
47 @cached_property
48 def cat_common(self) -> rdb.RdbCategory:
49 return self.report.create_category('Common')
51 @cached_property
52 def cat_pins(self) -> rdb.RdbCategory:
53 return self.report.create_category("Pins")
55 @cached_property
56 def cat_devices(self) -> rdb.RdbCategory:
57 return self.report.create_category("Devices")
59 @cached_property
60 def cat_vias(self) -> rdb.RdbCategory:
61 return self.report.create_category("Vias")
63 @cached_property
64 def cat_overlap(self) -> rdb.RdbCategory:
65 return self.report.create_category("[C] Overlap")
67 @cached_property
68 def cat_sidewall(self) -> rdb.RdbCategory:
69 return self.report.create_category("[C] Sidewall")
71 @cached_property
72 def cat_fringe(self) -> rdb.RdbCategory:
73 return self.report.create_category("[C] Fringe / Side Overlap")
75 @cached_property
76 def cat_edge_neighborhood(self) -> rdb.RdbCategory:
77 return self.report.create_category("[C] Edge Neighborhood Visitor")
79 def save(self, path: str):
80 self.report.save(path)
82 def output_shapes(self,
83 parent_category: rdb.RdbCategory,
84 category_name: str,
85 shapes: VarShapes) -> rdb.RdbCategory:
86 rdb_cat = self.report.create_category(parent_category, category_name)
87 self.report.create_items(self.cell.rdb_id(), ## TODO: if later hierarchical mode is introduced
88 rdb_cat.rdb_id(),
89 self.dbu_trans,
90 shapes)
91 return rdb_cat
93 def output_overlap(self,
94 overlap_cap: OverlapCap,
95 bottom_polygon: kdb.PolygonWithProperties,
96 top_polygon: kdb.PolygonWithProperties,
97 overlap_area: kdb.Region):
98 cat_overlap_top_layer = self.report.create_category(self.cat_overlap,
99 f"top_layer={overlap_cap.key.layer_top}")
100 cat_overlap_bot_layer = self.report.create_category(cat_overlap_top_layer,
101 f'bot_layer={overlap_cap.key.layer_bot}')
102 cat_overlap_nets = self.report.create_category(cat_overlap_bot_layer,
103 f'{overlap_cap.key.net_top} – {overlap_cap.key.net_bot}')
104 self.category_name_counter[cat_overlap_nets.path()] += 1
105 cat_overlap_cap = self.report.create_category(
106 cat_overlap_nets,
107 f"#{self.category_name_counter[cat_overlap_nets.path()]} "
108 f"{round(overlap_cap.cap_value, 3)} fF",
109 )
111 self.output_shapes(cat_overlap_cap, "Top Polygon", [top_polygon])
112 self.output_shapes(cat_overlap_cap, "Bottom Polygon", [bottom_polygon])
113 self.output_shapes(cat_overlap_cap, "Overlap Area", overlap_area)
115 def output_sidewall(self,
116 sidewall_cap: SidewallCap,
117 inside_edge: kdb.Edge,
118 outside_edge: kdb.Edge):
119 cat_sidewall_layer = self.report.create_category(self.cat_sidewall,
120 f"layer={sidewall_cap.key.layer}")
121 cat_sidewall_net_inside = self.report.create_category(cat_sidewall_layer,
122 f'inside={sidewall_cap.key.net1}')
123 cat_sidewall_net_outside = self.report.create_category(cat_sidewall_net_inside,
124 f'outside={sidewall_cap.key.net2}')
125 self.category_name_counter[cat_sidewall_net_outside.path()] += 1
127 self.output_shapes(
128 cat_sidewall_net_outside,
129 f"#{self.category_name_counter[cat_sidewall_net_outside.path()]}: "
130 f"len {sidewall_cap.length} µm, "
131 f"distance {sidewall_cap.distance} µm, "
132 f"{round(sidewall_cap.cap_value, 3)} fF",
133 [inside_edge, outside_edge]
134 )
136 def output_sideoverlap(self,
137 sideoverlap_cap: SideOverlapCap,
138 inside_edge: kdb.Edge,
139 outside_polygon: kdb.Polygon,
140 lateral_shield: Optional[kdb.Region]):
141 cat_sideoverlap_layer_inside = self.report.create_category(self.cat_fringe,
142 f"inside_layer={sideoverlap_cap.key.layer_inside}")
143 cat_sideoverlap_net_inside = self.report.create_category(cat_sideoverlap_layer_inside,
144 f'inside_net={sideoverlap_cap.key.net_inside}')
145 cat_sideoverlap_layer_outside = self.report.create_category(cat_sideoverlap_net_inside,
146 f'outside_layer={sideoverlap_cap.key.layer_outside}')
147 cat_sideoverlap_net_outside = self.report.create_category(cat_sideoverlap_layer_outside,
148 f'outside_net={sideoverlap_cap.key.net_outside}')
149 self.category_name_counter[cat_sideoverlap_net_outside.path()] += 1
151 cat_sideoverlap_cap = self.report.create_category(
152 cat_sideoverlap_net_outside,
153 f"#{self.category_name_counter[cat_sideoverlap_net_outside.path()]}: "
154 f"{round(sideoverlap_cap.cap_value, 3)} fF"
155 )
157 self.output_shapes(cat_sideoverlap_cap, 'Inside Edge', inside_edge)
159 shapes = kdb.Shapes()
160 shapes.insert(outside_polygon)
161 self.output_shapes(cat_sideoverlap_cap, 'Outside Polygon', shapes)
163 if lateral_shield is not None:
164 self.output_shapes(cat_sideoverlap_cap, 'Lateral Shield',
165 [lateral_shield])
167 def output_edge_neighborhood(self,
168 inside_layer: LayerName,
169 all_layer_names: List[LayerName],
170 edge: kdb.EdgeWithProperties,
171 neighborhood: EdgeNeighborhood,
172 geometry_restorer: GeometryRestorer):
173 cat_en_layer_inside = self.report.create_category(self.cat_edge_neighborhood, f"inside_layer={inside_layer}")
174 inside_net = edge.property('net')
175 cat_en_net_inside = self.report.create_category(cat_en_layer_inside, f'inside_net={inside_net}')
177 for edge_interval, polygons_by_child in neighborhood:
178 cat_en_edge_interval = self.report.create_category(cat_en_net_inside, f"Edge Interval: {edge_interval}")
179 self.category_name_counter[cat_en_edge_interval.path()] += 1
180 cat_en_edge = self.report.create_category(
181 cat_en_edge_interval,
182 f"#{self.category_name_counter[cat_en_edge_interval.path()]}"
183 )
184 self.output_shapes(cat_en_edge, "Edge", [edge]) # geometry_restorer.restore_edge(edge))
186 for child_index, polygons in polygons_by_child.items():
187 self.output_shapes(
188 cat_en_edge,
189 f"Child {child_index}: "
190 f"{child_index < len(all_layer_names) and all_layer_names[child_index] or 'None'}",
191 [geometry_restorer.restore_polygon(p) for p in polygons]
192 )
194 def output_devices(self,
195 devices_by_name: Dict[str, KLayoutDeviceInfo]):
196 for d in devices_by_name.values():
197 self.output_device(d)
199 def output_device(self,
200 device: KLayoutDeviceInfo):
201 cat_device = self.report.create_category(
202 self.cat_devices,
203 f"{device.name}: {device.class_name}"
204 )
205 cat_device_params = self.report.create_category(cat_device, 'Params')
206 for name, value in device.params.items():
207 self.report.create_category(cat_device_params, f"{name}: {value}")
209 cat_device_terminals = self.report.create_category(cat_device, 'Terminals')
210 for t in device.terminals.terminals:
211 if t.regions_by_layer_name:
212 for layer_name, regions in t.regions_by_layer_name.items():
213 self.output_shapes(
214 cat_device_terminals,
215 f"{t.name}: net {t.net_name}, layer {layer_name}",
216 regions
217 )
218 else:
219 self.report.create_category(
220 cat_device_terminals,
221 f"{t.name}: net <NOT CONNECTED> (TODO layer/shapes)",
222 )
224 def output_via(self,
225 via_name: LayerName,
226 bottom_layer: LayerName,
227 top_layer: LayerName,
228 net: str,
229 via_width: float,
230 via_spacing: float,
231 via_border: float,
232 polygon: kdb.Polygon,
233 ohm: float,
234 comment: str):
235 cat_via_layers = self.report.create_category(
236 self.cat_vias,
237 f"{via_name} ({bottom_layer} ↔ {top_layer}) (w={via_width}, sp={via_spacing}, b={via_border})"
238 )
240 self.category_name_counter[cat_via_layers.path()] += 1
242 self.output_shapes(
243 cat_via_layers,
244 f"#{self.category_name_counter[cat_via_layers.path()]} "
245 f"{ohm} Ω (net {net}) | {comment}",
246 [polygon]
247 )
249 def output_pin(self,
250 layer_name: LayerName,
251 pin_point: kdb.Box,
252 label: kdb.Text):
253 cat_pin_layer = self.report.create_category(self.cat_pins, layer_name)
254 sh = kdb.Shapes()
255 sh.insert(pin_point)
256 self.output_shapes(cat_pin_layer, label.string, sh)