Coverage for klayout_pex/tech_info.py: 92%
175 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 __future__ import annotations # allow class type hints within same class
27from typing import *
28from functools import cached_property
29import google.protobuf.json_format
31from .util.multiple_choice import MultipleChoicePattern
32import klayout_pex_protobuf.tech_pb2 as tech_pb2
33import klayout_pex_protobuf.process_stack_pb2 as process_stack_pb2
34import klayout_pex_protobuf.process_parasitics_pb2 as process_parasitics_pb2
36class TechInfo:
37 """Helper class for Protocol Buffer tech_pb2.Technology"""
39 GDSPair = Tuple[int, int]
41 @staticmethod
42 def parse_tech_def(jsonpb_path: str) -> tech_pb2.Technology:
43 with open(jsonpb_path, 'r') as f:
44 contents = f.read()
45 tech = google.protobuf.json_format.Parse(contents, tech_pb2.Technology())
46 return tech
48 @classmethod
49 def from_json(cls,
50 jsonpb_path: str,
51 dielectric_filter: Optional[MultipleChoicePattern]) -> TechInfo:
52 tech = cls.parse_tech_def(jsonpb_path=jsonpb_path)
53 return TechInfo(tech=tech,
54 dielectric_filter=dielectric_filter)
56 def __init__(self,
57 tech: tech_pb2.Technology,
58 dielectric_filter: Optional[MultipleChoicePattern]):
59 self.tech = tech
60 self.dielectric_filter = dielectric_filter or MultipleChoicePattern(pattern='all')
62 @cached_property
63 def gds_pair_for_computed_layer_name(self) -> Dict[str, GDSPair]:
64 return {lyr.layer_info.name: (lyr.layer_info.drw_gds_pair.layer, lyr.layer_info.drw_gds_pair.datatype)
65 for lyr in self.tech.lvs_computed_layers}
67 @cached_property
68 def computed_layer_info_by_name(self) -> Dict[str, tech_pb2.ComputedLayerInfo]:
69 return {lyr.layer_info.name: lyr for lyr in self.tech.lvs_computed_layers}
71 @cached_property
72 def computed_layer_info_by_gds_pair(self) -> Dict[GDSPair, tech_pb2.ComputedLayerInfo]:
73 return {
74 (lyr.layer_info.drw_gds_pair.layer, lyr.layer_info.drw_gds_pair.datatype): lyr
75 for lyr in self.tech.lvs_computed_layers
76 }
78 @cached_property
79 def canonical_layer_name_by_gds_pair(self) -> Dict[GDSPair, str]:
80 return {
81 (lyr.layer_info.drw_gds_pair.layer, lyr.layer_info.drw_gds_pair.datatype): lyr.original_layer_name
82 for lyr in self.tech.lvs_computed_layers
83 }
85 @cached_property
86 def layer_info_by_name(self) -> Dict[str, tech_pb2.LayerInfo]:
87 return {lyr.name: lyr for lyr in self.tech.layers}
89 @cached_property
90 def pin_layer_mapping_for_drw_gds_pair(self) -> Dict[GDSPair, tech_pb2.PinLayerMapping]:
91 return {
92 (m.drw_gds_layer, m.drw_gds_datatype): (m.pin_gds_layer, m.pin_gds_datatype)
93 for m in self.tech.pin_layer_mappings
94 }
96 @cached_property
97 def gds_pair_for_layer_name(self) -> Dict[str, GDSPair]:
98 return {lyr.name: (lyr.drw_gds_pair.layer, lyr.drw_gds_pair.datatype) for lyr in self.tech.layers}
100 @cached_property
101 def layer_info_by_gds_pair(self) -> Dict[GDSPair, tech_pb2.LayerInfo]:
102 return {(lyr.drw_gds_pair.layer, lyr.drw_gds_pair.datatype): lyr for lyr in self.tech.layers}
104 @cached_property
105 def process_stack_layer_by_name(self) -> Dict[str, process_stack_pb2.ProcessStackInfo.LayerInfo]:
106 return {lyr.name: lyr for lyr in self.tech.process_stack.layers}
108 @cached_property
109 def process_stack_layer_by_gds_pair(self) -> Dict[GDSPair, process_stack_pb2.ProcessStackInfo.LayerInfo]:
110 return {
111 (lyr.drw_gds_pair.layer, lyr.drw_gds_pair.datatype): self.process_stack_layer_by_name[lyr.name]
112 for lyr in self.tech.process_stack.layers
113 }
115 @cached_property
116 def process_substrate_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
117 return list(
118 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SUBSTRATE,
119 self.tech.process_stack.layers)
120 )[0]
122 @cached_property
123 def process_diffusion_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]:
124 return list(
125 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_DIFFUSION,
126 self.tech.process_stack.layers)
127 )
129 @cached_property
130 def gate_poly_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
131 return self.process_metal_layers[0]
133 @cached_property
134 def field_oxide_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
135 return list(
136 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_FIELD_OXIDE,
137 self.tech.process_stack.layers)
138 )[0]
140 @cached_property
141 def process_metal_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]:
142 return list(
143 filter(lambda lyr: lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL,
144 self.tech.process_stack.layers)
145 )
147 @cached_property
148 def filtered_dielectric_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]:
149 layers = []
150 for pl in self.tech.process_stack.layers:
151 match pl.layer_type:
152 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC | \
153 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC | \
154 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
155 if self.dielectric_filter.is_included(pl.name):
156 layers.append(pl)
157 return layers
159 @cached_property
160 def dielectric_by_name(self) -> Dict[str, float]:
161 diel_by_name = {}
162 for pl in self.filtered_dielectric_layers:
163 match pl.layer_type:
164 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC:
165 diel_by_name[pl.name] = pl.simple_dielectric_layer.dielectric_k
166 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
167 diel_by_name[pl.name] = pl.conformal_dielectric_layer.dielectric_k
168 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
169 diel_by_name[pl.name] = pl.sidewall_dielectric_layer.dielectric_k
170 return diel_by_name
172 def sidewall_dielectric_layer(self, layer_name: str) -> Optional[process_stack_pb2.ProcessStackInfo.LayerInfo]:
173 found_layers: List[process_stack_pb2.ProcessStackInfo.LayerInfo] = []
174 for lyr in self.filtered_dielectric_layers:
175 match lyr.layer_type:
176 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
177 if lyr.sidewall_dielectric_layer.reference == layer_name:
178 found_layers.append(lyr)
179 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
180 if lyr.conformal_dielectric_layer.reference == layer_name:
181 found_layers.append(lyr)
182 case _:
183 continue
185 if len(found_layers) == 0:
186 return None
187 if len(found_layers) >= 2:
188 raise Exception(f"found multiple sidewall dielectric layers for {layer_name}")
189 return found_layers[0]
191 def simple_dielectric_above_metal(self, layer_name: str) -> Tuple[Optional[process_stack_pb2.ProcessStackInfo.LayerInfo], float]:
192 """
193 Returns a tuple of the dielectric layer and it's (maximum) height.
194 Maximum would be the case where no metal and other dielectrics are present.
195 """
196 found_layer: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None
197 diel_lyr: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None
198 for lyr in self.tech.process_stack.layers:
199 if lyr.name == layer_name:
200 found_layer = lyr
201 elif found_layer:
202 if not diel_lyr and lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC:
203 if not self.dielectric_filter.is_included(lyr.name):
204 return None, 0.0
205 diel_lyr = lyr
206 # search for next metal or end of stack
207 if lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL:
208 return diel_lyr, lyr.metal_layer.height - found_layer.metal_layer.height
209 return diel_lyr, 5.0 # air TODO
211 @cached_property
212 def contact_above_metal_layer_name(self) -> Dict[str, process_stack_pb2.ProcessStackInfo.Contact]:
213 return {lyr.name: lyr.metal_layer.contact_above
214 for lyr in self.process_metal_layers}
216 @cached_property
217 def contact_by_contact_layer_name(self) -> Dict[str, process_stack_pb2.ProcessStackInfo.Contact]:
218 return {lyr.metal_layer.contact_above.name: lyr.metal_layer.contact_above
219 for lyr in self.process_metal_layers}
221 #--------------------------------
223 @cached_property
224 def layer_resistance_by_layer_name(self) -> Dict[str, process_parasitics_pb2.ResistanceInfo.LayerResistance]:
225 return {r.layer_name: r for r in self.tech.process_parasitics.resistance.layers}
227 @cached_property
228 def contact_resistance_by_layer_name(self) -> Dict[str, process_parasitics_pb2.ResistanceInfo.ContactResistance]:
229 return {r.device_layer_name: r for r in self.tech.process_parasitics.resistance.contacts}
231 @cached_property
232 def via_resistance_by_layer_name(self) -> Dict[str, process_parasitics_pb2.ResistanceInfo.ViaResistance]:
233 return {r.via_name: r for r in self.tech.process_parasitics.resistance.vias}
235 #--------------------------------
237 @cached_property
238 def substrate_cap_by_layer_name(self) -> Dict[str, process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance]:
239 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.substrates}
241 @cached_property
242 def overlap_cap_by_layer_names(self) -> Dict[str, Dict[str, process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance]]:
243 """
244 usage: dict[top_layer_name][bottom_layer_name]
245 """
247 def convert_substrate_to_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \
248 -> process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance:
249 oc = process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance()
250 oc.top_layer_name = sc.layer_name
251 oc.bottom_layer_name = self.internal_substrate_layer_name
252 oc.capacitance = sc.area_capacitance
253 return oc
255 d = {
256 ln: {
257 self.internal_substrate_layer_name: convert_substrate_to_overlap_cap(sc)
258 } for ln, sc in self.substrate_cap_by_layer_name.items()
259 }
261 d2 = {
262 oc.top_layer_name: {
263 oc_bot.bottom_layer_name: oc_bot
264 for oc_bot in self.tech.process_parasitics.capacitance.overlaps if oc_bot.top_layer_name == oc.top_layer_name
265 }
266 for oc in self.tech.process_parasitics.capacitance.overlaps
267 }
269 for k1, ve in d2.items():
270 for k2, v in ve.items():
271 if k1 not in d:
272 d[k1] = {k2: v}
273 else:
274 d[k1][k2] = v
275 return d
277 @cached_property
278 def sidewall_cap_by_layer_name(self) -> Dict[str, process_parasitics_pb2.CapacitanceInfo.SidewallCapacitance]:
279 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.sidewalls}
281 @classmethod
282 @property
283 def internal_substrate_layer_name(cls) -> str:
284 return 'VSUBS'
286 @cached_property
287 def side_overlap_cap_by_layer_names(self) -> Dict[str, Dict[str, process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance]]:
288 """
289 usage: dict[in_layer_name][out_layer_name]
290 """
292 def convert_substrate_to_side_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \
293 -> process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance:
294 soc = process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance()
295 soc.in_layer_name = sc.layer_name
296 soc.out_layer_name = self.internal_substrate_layer_name
297 soc.capacitance = sc.perimeter_capacitance
298 return soc
300 d = {
301 ln: {
302 self.internal_substrate_layer_name: convert_substrate_to_side_overlap_cap(sc)
303 } for ln, sc in self.substrate_cap_by_layer_name.items()
304 }
306 d2 = {
307 oc.in_layer_name: {
308 oc_bot.out_layer_name: oc_bot
309 for oc_bot in self.tech.process_parasitics.capacitance.sideoverlaps if oc_bot.in_layer_name == oc.in_layer_name
310 }
311 for oc in self.tech.process_parasitics.capacitance.sideoverlaps
312 }
314 for k1, ve in d2.items():
315 for k2, v in ve.items():
316 d[k1][k2] = v
318 return d