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

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# 

25 

26from __future__ import annotations # allow class type hints within same class 

27from typing import * 

28from functools import cached_property 

29import google.protobuf.json_format 

30 

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 

35 

36class TechInfo: 

37 """Helper class for Protocol Buffer tech_pb2.Technology""" 

38 

39 GDSPair = Tuple[int, int] 

40 

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 

47 

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) 

55 

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') 

61 

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} 

66 

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} 

70 

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 } 

77 

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 } 

84 

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} 

88 

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} 

92 

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} 

96 

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} 

100 

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 } 

107 

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] 

114 

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 ) 

121 

122 @cached_property 

123 def gate_poly_layer(self) -> process_stack_pb2.ProcessStackInfo.LayerInfo: 

124 return self.process_metal_layers[0] 

125 

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] 

132 

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 ) 

139 

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 

151 

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 

164 

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 

177 

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] 

183 

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 

203 

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} 

207 

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 """ 

213 

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 

221 

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 } 

227 

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 } 

235 

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 

243 

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} 

247 

248 @classmethod 

249 @property 

250 def internal_substrate_layer_name(cls) -> str: 

251 return 'VSUBS' 

252 

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 """ 

258 

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 

266 

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 } 

272 

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 } 

280 

281 for k1, ve in d2.items(): 

282 for k2, v in ve.items(): 

283 d[k1][k2] = v 

284 

285 return d