Coverage for klayout_pex/tech_info.py: 95%
157 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#! /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.gds_layer, lyr.layer_info.gds_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.gds_layer, lyr.layer_info.gds_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.gds_layer, lyr.layer_info.gds_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 gds_pair_for_layer_name(self) -> Dict[str, GDSPair]:
91 return {lyr.name: (lyr.gds_layer, lyr.gds_datatype) for lyr in self.tech.layers}
93 @cached_property
94 def layer_info_by_gds_pair(self) -> Dict[GDSPair, tech_pb2.LayerInfo]:
95 return {(lyr.gds_layer, lyr.gds_datatype): lyr for lyr in self.tech.layers}
97 @cached_property
98 def process_stack_layer_by_name(self) -> Dict[str, process_stack_pb2.ProcessStackInfo.LayerInfo]:
99 return {lyr.name: lyr for lyr in self.tech.process_stack.layers}
101 @cached_property
102 def process_stack_layer_by_gds_pair(self) -> Dict[GDSPair, process_stack_pb2.ProcessStackInfo.LayerInfo]:
103 return {
104 (lyr.gds_layer, lyr.gds_datatype): self.process_stack_layer_by_name[lyr.name]
105 for lyr in self.tech.process_stack.layers
106 }
108 @cached_property
109 def process_substrate_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
110 return list(
111 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SUBSTRATE,
112 self.tech.process_stack.layers)
113 )[0]
115 @cached_property
116 def process_diffusion_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]:
117 return list(
118 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_DIFFUSION,
119 self.tech.process_stack.layers)
120 )
122 @cached_property
123 def gate_poly_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
124 return self.process_metal_layers[0]
126 @cached_property
127 def field_oxide_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo:
128 return list(
129 filter(lambda lyr: lyr.layer_type is process_stack_pb2.ProcessStackInfo.LAYER_TYPE_FIELD_OXIDE,
130 self.tech.process_stack.layers)
131 )[0]
133 @cached_property
134 def process_metal_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]:
135 return list(
136 filter(lambda lyr: lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL,
137 self.tech.process_stack.layers)
138 )
140 @cached_property
141 def filtered_dielectric_layers(self) -> List[process_stack_pb2.ProcessStackInfo.LayerInfo]:
142 layers = []
143 for pl in self.tech.process_stack.layers:
144 match pl.layer_type:
145 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC | \
146 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC | \
147 process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
148 if self.dielectric_filter.is_included(pl.name):
149 layers.append(pl)
150 return layers
152 @cached_property
153 def dielectric_by_name(self) -> Dict[str, float]:
154 diel_by_name = {}
155 for pl in self.filtered_dielectric_layers:
156 match pl.layer_type:
157 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC:
158 diel_by_name[pl.name] = pl.simple_dielectric_layer.dielectric_k
159 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
160 diel_by_name[pl.name] = pl.conformal_dielectric_layer.dielectric_k
161 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
162 diel_by_name[pl.name] = pl.sidewall_dielectric_layer.dielectric_k
163 return diel_by_name
165 def sidewall_dielectric_layer(self, layer_name: str) -> Optional[process_stack_pb2.ProcessStackInfo.LayerInfo]:
166 found_layers: List[process_stack_pb2.ProcessStackInfo.LayerInfo] = []
167 for lyr in self.filtered_dielectric_layers:
168 match lyr.layer_type:
169 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIDEWALL_DIELECTRIC:
170 if lyr.sidewall_dielectric_layer.reference == layer_name:
171 found_layers.append(lyr)
172 case process_stack_pb2.ProcessStackInfo.LAYER_TYPE_CONFORMAL_DIELECTRIC:
173 if lyr.conformal_dielectric_layer.reference == layer_name:
174 found_layers.append(lyr)
175 case _:
176 continue
178 if len(found_layers) == 0:
179 return None
180 if len(found_layers) >= 2:
181 raise Exception(f"found multiple sidewall dielectric layers for {layer_name}")
182 return found_layers[0]
184 def simple_dielectric_above_metal(self, layer_name: str) -> Tuple[Optional[process_stack_pb2.ProcessStackInfo.LayerInfo], float]:
185 """
186 Returns a tuple of the dielectric layer and it's (maximum) height.
187 Maximum would be the case where no metal and other dielectrics are present.
188 """
189 found_layer: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None
190 diel_lyr: Optional[process_stack_pb2.ProcessStackInfo.LayerInfo] = None
191 for lyr in self.tech.process_stack.layers:
192 if lyr.name == layer_name:
193 found_layer = lyr
194 elif found_layer:
195 if not diel_lyr and lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_SIMPLE_DIELECTRIC:
196 if not self.dielectric_filter.is_included(lyr.name):
197 return None, 0.0
198 diel_lyr = lyr
199 # search for next metal or end of stack
200 if lyr.layer_type == process_stack_pb2.ProcessStackInfo.LAYER_TYPE_METAL:
201 return diel_lyr, lyr.metal_layer.height - found_layer.metal_layer.height
202 return diel_lyr, 5.0 # air TODO
204 @cached_property
205 def substrate_cap_by_layer_name(self) -> Dict[str, process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance]:
206 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.substrates}
208 @cached_property
209 def overlap_cap_by_layer_names(self) -> Dict[str, Dict[str, process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance]]:
210 """
211 usage: dict[top_layer_name][bottom_layer_name]
212 """
214 def convert_substrate_to_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \
215 -> process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance:
216 oc = process_parasitics_pb2.CapacitanceInfo.OverlapCapacitance()
217 oc.top_layer_name = sc.layer_name
218 oc.bottom_layer_name = self.internal_substrate_layer_name
219 oc.capacitance = sc.area_capacitance
220 return oc
222 d = {
223 ln: {
224 self.internal_substrate_layer_name: convert_substrate_to_overlap_cap(sc)
225 } for ln, sc in self.substrate_cap_by_layer_name.items()
226 }
228 d2 = {
229 oc.top_layer_name: {
230 oc_bot.bottom_layer_name: oc_bot
231 for oc_bot in self.tech.process_parasitics.capacitance.overlaps if oc_bot.top_layer_name == oc.top_layer_name
232 }
233 for oc in self.tech.process_parasitics.capacitance.overlaps
234 }
236 for k1, ve in d2.items():
237 for k2, v in ve.items():
238 if k1 not in d:
239 d[k1] = {k2: v}
240 else:
241 d[k1][k2] = v
242 return d
244 @cached_property
245 def sidewall_cap_by_layer_name(self) -> Dict[str, process_parasitics_pb2.CapacitanceInfo.SidewallCapacitance]:
246 return {sc.layer_name: sc for sc in self.tech.process_parasitics.capacitance.sidewalls}
248 @classmethod
249 @property
250 def internal_substrate_layer_name(cls) -> str:
251 return 'VSUBS'
253 @cached_property
254 def side_overlap_cap_by_layer_names(self) -> Dict[str, Dict[str, process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance]]:
255 """
256 usage: dict[in_layer_name][out_layer_name]
257 """
259 def convert_substrate_to_side_overlap_cap(sc: process_parasitics_pb2.CapacitanceInfo.SubstrateCapacitance) \
260 -> process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance:
261 soc = process_parasitics_pb2.CapacitanceInfo.SideOverlapCapacitance()
262 soc.in_layer_name = sc.layer_name
263 soc.out_layer_name = self.internal_substrate_layer_name
264 soc.capacitance = sc.perimeter_capacitance
265 return soc
267 d = {
268 ln: {
269 self.internal_substrate_layer_name: convert_substrate_to_side_overlap_cap(sc)
270 } for ln, sc in self.substrate_cap_by_layer_name.items()
271 }
273 d2 = {
274 oc.in_layer_name: {
275 oc_bot.out_layer_name: oc_bot
276 for oc_bot in self.tech.process_parasitics.capacitance.sideoverlaps if oc_bot.in_layer_name == oc.in_layer_name
277 }
278 for oc in self.tech.process_parasitics.capacitance.sideoverlaps
279 }
281 for k1, ve in d2.items():
282 for k2, v in ve.items():
283 d[k1][k2] = v
285 return d