Coverage for klayout_pex/klayout/lvsdb_extractor.py: 82%
128 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#
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#
24from __future__ import annotations
26import tempfile
27from typing import *
28from dataclasses import dataclass
29from rich.pretty import pprint
31import klayout.db as kdb
33import klayout_pex_protobuf.tech_pb2 as tech_pb2
34from ..log import (
35 console,
36 debug,
37 info,
38 warning,
39 error,
40 rule
41)
43from ..tech_info import TechInfo
46GDSPair = Tuple[int, int]
49@dataclass
50class KLayoutExtractedLayerInfo:
51 index: int
52 lvs_layer_name: str # NOTE: this can be computed, so gds_pair is preferred
53 gds_pair: GDSPair
54 region: kdb.Region
57@dataclass
58class KLayoutMergedExtractedLayerInfo:
59 source_layers: List[KLayoutExtractedLayerInfo]
60 gds_pair: GDSPair
63@dataclass
64class KLayoutExtractionContext:
65 lvsdb: kdb.LayoutToNetlist
66 dbu: float
67 top_cell: kdb.Cell
68 layer_map: Dict[int, kdb.LayerInfo]
69 cell_mapping: kdb.CellMapping
70 target_layout: kdb.Layout
71 extracted_layers: Dict[GDSPair, KLayoutMergedExtractedLayerInfo]
72 unnamed_layers: List[KLayoutExtractedLayerInfo]
74 @classmethod
75 def prepare_extraction(cls,
76 lvsdb: kdb.LayoutToNetlist,
77 top_cell: str,
78 tech: TechInfo,
79 blackbox_devices: bool) -> KLayoutExtractionContext:
80 dbu = lvsdb.internal_layout().dbu
81 target_layout = kdb.Layout()
82 target_layout.dbu = dbu
83 top_cell = target_layout.create_cell(top_cell)
85 # CellMapping
86 # mapping of internal layout to target layout for the circuit mapping
87 # https://www.klayout.de/doc-qt5/code/class_CellMapping.html
88 # ---
89 # https://www.klayout.de/doc-qt5/code/class_LayoutToNetlist.html#method18
90 # Creates a cell mapping for copying shapes from the internal layout to the given target layout
91 cm = lvsdb.cell_mapping_into(target_layout, # target layout
92 top_cell,
93 not blackbox_devices) # with_device_cells
95 lm = cls.build_LVS_layer_map(target_layout=target_layout,
96 lvsdb=lvsdb,
97 tech=tech,
98 blackbox_devices=blackbox_devices)
100 net_name_prop_num = 1
102 # Build a full hierarchical representation of the nets
103 # https://www.klayout.de/doc-qt5/code/class_LayoutToNetlist.html#method14
104 # hier_mode = None
105 hier_mode = kdb.LayoutToNetlist.BuildNetHierarchyMode.BNH_Flatten
106 # hier_mode = kdb.LayoutToNetlist.BuildNetHierarchyMode.BNH_SubcircuitCells
108 lvsdb.build_all_nets(
109 cmap=cm, # mapping of internal layout to target layout for the circuit mapping
110 target=target_layout, # target layout
111 lmap=lm, # maps: target layer index => net regions
112 hier_mode=hier_mode, # hier mode
113 netname_prop=net_name_prop_num, # property name to which to attach the net name
114 circuit_cell_name_prefix="CIRCUIT_",
115 device_cell_name_prefix=None # "DEVICE_"
116 )
118 extracted_layers, unnamed_layers = cls.nonempty_extracted_layers(lvsdb=lvsdb,
119 tech=tech,
120 blackbox_devices=blackbox_devices)
122 return KLayoutExtractionContext(
123 lvsdb=lvsdb,
124 dbu=dbu,
125 top_cell=top_cell,
126 layer_map=lm,
127 cell_mapping=cm,
128 target_layout=target_layout,
129 extracted_layers=extracted_layers,
130 unnamed_layers=unnamed_layers
131 )
133 @staticmethod
134 def build_LVS_layer_map(target_layout: kdb.Layout,
135 lvsdb: kdb.LayoutToNetlist,
136 tech: TechInfo,
137 blackbox_devices: bool) -> Dict[int, kdb.LayerInfo]:
138 # NOTE: currently, the layer numbers are auto-assigned
139 # by the sequence they occur in the LVS script, hence not well defined!
140 # build a layer map for the layers that correspond to original ones.
142 # https://www.klayout.de/doc-qt5/code/class_LayerInfo.html
143 lm: Dict[int, kdb.LayerInfo] = {}
145 if not hasattr(lvsdb, "layer_indexes"):
146 raise Exception("Needs at least KLayout version 0.29.2")
148 for layer_index in lvsdb.layer_indexes():
149 lname = lvsdb.layer_name(layer_index)
151 computed_layer_info = tech.computed_layer_info_by_name.get(lname, None)
152 if computed_layer_info and blackbox_devices:
153 match computed_layer_info.kind:
154 case tech_pb2.ComputedLayerInfo.Kind.KIND_DEVICE_RESISTOR:
155 continue
156 case tech_pb2.ComputedLayerInfo.Kind.KIND_DEVICE_CAPACITOR:
157 continue
159 gds_pair = tech.gds_pair_for_computed_layer_name.get(lname, None)
160 if not gds_pair:
161 li = lvsdb.internal_layout().get_info(layer_index)
162 if li != kdb.LayerInfo():
163 gds_pair = (li.layer, li.datatype)
165 if gds_pair is not None:
166 target_layer_index = target_layout.layer(*gds_pair) # Creates a new internal layer!
167 region = lvsdb.layer_by_index(layer_index)
168 lm[target_layer_index] = region
170 return lm
172 @staticmethod
173 def nonempty_extracted_layers(lvsdb: kdb.LayoutToNetlist,
174 tech: TechInfo,
175 blackbox_devices: bool) -> Tuple[Dict[GDSPair, KLayoutMergedExtractedLayerInfo], List[KLayoutExtractedLayerInfo]]:
176 # https://www.klayout.de/doc-qt5/code/class_LayoutToNetlist.html#method18
177 nonempty_layers: Dict[GDSPair, KLayoutMergedExtractedLayerInfo] = {}
179 unnamed_layers: List[KLayoutExtractedLayerInfo] = []
181 for idx, ln in enumerate(lvsdb.layer_names()):
182 layer = lvsdb.layer_by_name(ln)
183 if layer.count() >= 1:
184 computed_layer_info = tech.computed_layer_info_by_name.get(ln, None)
185 if not computed_layer_info:
186 warning(f"Unable to find info about extracted LVS layer '{ln}'")
187 gds_pair = (1000 + idx, 20)
188 linfo = KLayoutExtractedLayerInfo(
189 index=idx,
190 lvs_layer_name=ln,
191 gds_pair=gds_pair,
192 region=layer
193 )
194 unnamed_layers.append(linfo)
195 continue
197 if blackbox_devices:
198 match computed_layer_info.kind:
199 case tech_pb2.ComputedLayerInfo.Kind.KIND_DEVICE_RESISTOR:
200 continue
201 case tech_pb2.ComputedLayerInfo.Kind.KIND_DEVICE_CAPACITOR:
202 continue
204 gds_pair = (computed_layer_info.layer_info.gds_layer, computed_layer_info.layer_info.gds_datatype)
206 linfo = KLayoutExtractedLayerInfo(
207 index=idx,
208 lvs_layer_name=ln,
209 gds_pair=gds_pair,
210 region=layer
211 )
213 entry = nonempty_layers.get(gds_pair, None)
214 if entry:
215 entry.source_layers.append(linfo)
216 else:
217 nonempty_layers[gds_pair] = KLayoutMergedExtractedLayerInfo(
218 source_layers=[linfo],
219 gds_pair=gds_pair,
220 )
222 return nonempty_layers, unnamed_layers
224 def top_cell_bbox(self) -> kdb.Box:
225 b1: kdb.Box = self.target_layout.top_cell().bbox()
226 b2: kdb.Box = self.lvsdb.internal_layout().top_cell().bbox()
227 if b1.area() > b2.area():
228 return b1
229 else:
230 return b2
232 def shapes_of_net(self, gds_pair: GDSPair, net: kdb.Net) -> Optional[kdb.Region]:
233 lyr = self.extracted_layers.get(gds_pair, None)
234 if not lyr:
235 return None
237 shapes: kdb.Region
239 match len(lyr.source_layers):
240 case 0:
241 raise AssertionError('Internal error: Empty list of source_layers')
242 case 1:
243 shapes = self.lvsdb.shapes_of_net(net, lyr.source_layers[0].region, True)
244 case _:
245 shapes = kdb.Region()
246 for sl in lyr.source_layers:
247 shapes += self.lvsdb.shapes_of_net(net, sl.region, True)
248 # shapes.merge()
250 return shapes
252 def shapes_of_layer(self, gds_pair: GDSPair) -> Optional[kdb.Region]:
253 lyr = self.extracted_layers.get(gds_pair, None)
254 if not lyr:
255 return None
257 shapes: kdb.Region
259 match len(lyr.source_layers):
260 case 0:
261 raise AssertionError('Internal error: Empty list of source_layers')
262 case 1:
263 shapes = lyr.source_layers[0].region
264 case _:
265 shapes = kdb.Region()
266 for sl in lyr.source_layers:
267 shapes += sl.region
268 # shapes.merge()
270 return shapes