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

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 

25 

26import tempfile 

27from typing import * 

28from dataclasses import dataclass 

29from rich.pretty import pprint 

30 

31import klayout.db as kdb 

32 

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) 

42 

43from ..tech_info import TechInfo 

44 

45 

46GDSPair = Tuple[int, int] 

47 

48 

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 

55 

56 

57@dataclass 

58class KLayoutMergedExtractedLayerInfo: 

59 source_layers: List[KLayoutExtractedLayerInfo] 

60 gds_pair: GDSPair 

61 

62 

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] 

73 

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) 

84 

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 

94 

95 lm = cls.build_LVS_layer_map(target_layout=target_layout, 

96 lvsdb=lvsdb, 

97 tech=tech, 

98 blackbox_devices=blackbox_devices) 

99 

100 net_name_prop_num = 1 

101 

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 

107 

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 ) 

117 

118 extracted_layers, unnamed_layers = cls.nonempty_extracted_layers(lvsdb=lvsdb, 

119 tech=tech, 

120 blackbox_devices=blackbox_devices) 

121 

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 ) 

132 

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. 

141 

142 # https://www.klayout.de/doc-qt5/code/class_LayerInfo.html 

143 lm: Dict[int, kdb.LayerInfo] = {} 

144 

145 if not hasattr(lvsdb, "layer_indexes"): 

146 raise Exception("Needs at least KLayout version 0.29.2") 

147 

148 for layer_index in lvsdb.layer_indexes(): 

149 lname = lvsdb.layer_name(layer_index) 

150 

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 

158 

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) 

164 

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 

169 

170 return lm 

171 

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] = {} 

178 

179 unnamed_layers: List[KLayoutExtractedLayerInfo] = [] 

180 

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 

196 

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 

203 

204 gds_pair = (computed_layer_info.layer_info.gds_layer, computed_layer_info.layer_info.gds_datatype) 

205 

206 linfo = KLayoutExtractedLayerInfo( 

207 index=idx, 

208 lvs_layer_name=ln, 

209 gds_pair=gds_pair, 

210 region=layer 

211 ) 

212 

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 ) 

221 

222 return nonempty_layers, unnamed_layers 

223 

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 

231 

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 

236 

237 shapes: kdb.Region 

238 

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

249 

250 return shapes 

251 

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 

256 

257 shapes: kdb.Region 

258 

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

269 

270 return shapes 

271 

272