Coverage for klayout_pex/rcx25/extractor.py: 21%

103 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-08 18:54 +0000

1#! /usr/bin/env python3 

2# 

3# -------------------------------------------------------------------------------- 

4# SPDX-FileCopyrightText: 2024-2025 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 

26import klayout.db as kdb 

27 

28from ..klayout.lvsdb_extractor import KLayoutExtractionContext, GDSPair 

29from ..log import ( 

30 debug, 

31 warning, 

32 error, 

33 info, 

34 subproc, 

35 rule 

36) 

37from ..tech_info import TechInfo 

38from .extraction_results import * 

39from .extraction_reporter import ExtractionReporter 

40from .pex_mode import PEXMode 

41from klayout_pex.rcx25.c.overlap_extractor import OverlapExtractor 

42from klayout_pex.rcx25.c.sidewall_and_fringe_extractor import SidewallAndFringeExtractor 

43from klayout_pex.rcx25.r.r_extractor import RExtractor 

44 

45import klayout_pex_protobuf.kpex.geometry.shapes_pb2 as shapes_pb2 

46import klayout_pex_protobuf.kpex.layout.location_pb2 as location_pb2 

47import klayout_pex_protobuf.kpex.request.pex_request_pb2 as pex_request_pb2 

48import klayout_pex_protobuf.kpex.result.pex_result_pb2 as pex_result_pb2 

49import klayout_pex_protobuf.kpex.klayout.r_extractor_tech_pb2 as rex_tech_pb2 

50from klayout_pex_protobuf.kpex.klayout.r_extractor_tech_pb2 import RExtractorTech as pb_RExtractorTech 

51 

52 

53class RCX25Extractor: 

54 def __init__(self, 

55 pex_context: KLayoutExtractionContext, 

56 pex_mode: PEXMode, 

57 scale_ratio_to_fit_halo: bool, 

58 delaunay_amax: float, 

59 delaunay_b: float, 

60 tech_info: TechInfo, 

61 report_path: str): 

62 self.pex_context = pex_context 

63 self.pex_mode = pex_mode 

64 self.scale_ratio_to_fit_halo = scale_ratio_to_fit_halo 

65 self.delaunay_amax = delaunay_amax 

66 self.delaunay_b = delaunay_b 

67 self.tech_info = tech_info 

68 self.report_path = report_path 

69 

70 if "PolygonWithProperties" not in kdb.__all__: 

71 raise Exception("KLayout version does not support properties (needs 0.30 at least)") 

72 

73 # TODO: remove this function by inlining 

74 def gds_pair(self, layer_name) -> Optional[GDSPair]: 

75 return self.tech_info.gds_pair(layer_name) 

76 

77 def shapes_of_layer(self, layer_name: str) -> Optional[kdb.Region]: 

78 gds_pair = self.gds_pair(layer_name=layer_name) 

79 if not gds_pair: 

80 return None 

81 

82 shapes = self.pex_context.shapes_of_layer(gds_pair=gds_pair) 

83 if not shapes: 

84 debug(f"Nothing extracted for layer {layer_name}") 

85 

86 return shapes 

87 

88 def extract(self) -> ExtractionResults: 

89 extraction_results = ExtractionResults() 

90 

91 # TODO: for now, we always flatten and have only 1 cell 

92 cell_name = self.pex_context.annotated_top_cell.name 

93 extraction_report = ExtractionReporter(cell_name=cell_name, 

94 dbu=self.pex_context.dbu) 

95 cell_extraction_results = CellExtractionResults(cell_name=cell_name) 

96 

97 # Explicitly log the stacktrace here, because otherwise Exceptions  

98 # raised in the callbacks of *NeighborhoodVisitors can cause RuntimeErrors 

99 # that are not traceable beyond the Region.complex_op() calls 

100 try: 

101 self.extract_cell(results=cell_extraction_results, 

102 report=extraction_report) 

103 except RuntimeError as e: 

104 import traceback 

105 print(f"Caught a RuntimeError: {e}") 

106 traceback.print_exc() 

107 raise 

108 

109 extraction_results.cell_extraction_results[cell_name] = cell_extraction_results 

110 

111 extraction_report.save(self.report_path) 

112 

113 return extraction_results 

114 

115 def extract_cell(self, 

116 results: CellExtractionResults, 

117 report: ExtractionReporter): 

118 netlist: kdb.Netlist = self.pex_context.lvsdb.netlist() 

119 dbu = self.pex_context.dbu 

120 # ------------------------------------------------------------------------ 

121 

122 layer_regions_by_name: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region) 

123 

124 all_region = kdb.Region() 

125 all_region.enable_properties() 

126 

127 substrate_region = kdb.Region() 

128 substrate_region.enable_properties() 

129 

130 side_halo_um = self.tech_info.tech.process_parasitics.side_halo 

131 substrate_region.insert(self.pex_context.top_cell_bbox().enlarged(side_halo_um / dbu)) # e.g. 8 µm halo 

132 

133 layer_regions_by_name[self.tech_info.internal_substrate_layer_name] = substrate_region 

134 

135 via_name_below_layer_name: Dict[LayerName, Optional[LayerName]] = {} 

136 via_name_above_layer_name: Dict[LayerName, Optional[LayerName]] = {} 

137 via_regions_by_via_name: Dict[LayerName, kdb.Region] = defaultdict(kdb.Region) 

138 

139 previous_via_name: Optional[str] = None 

140 

141 for metal_layer in self.tech_info.process_metal_layers: 

142 layer_name = metal_layer.name 

143 gds_pair = self.gds_pair(layer_name) 

144 canonical_layer_name = self.tech_info.canonical_layer_name_by_gds_pair[gds_pair] 

145 

146 all_layer_shapes = self.shapes_of_layer(layer_name) 

147 if all_layer_shapes is not None: 

148 all_layer_shapes.enable_properties() 

149 

150 layer_regions_by_name[canonical_layer_name] += all_layer_shapes 

151 layer_regions_by_name[canonical_layer_name].enable_properties() 

152 all_region += all_layer_shapes 

153 

154 if metal_layer.metal_layer.HasField('contact_above'): 

155 contact = metal_layer.metal_layer.contact_above 

156 

157 via_regions = self.shapes_of_layer(contact.name) 

158 if via_regions is not None: 

159 via_regions.enable_properties() 

160 via_regions_by_via_name[contact.name] += via_regions 

161 via_name_above_layer_name[canonical_layer_name] = contact.name 

162 via_name_below_layer_name[canonical_layer_name] = previous_via_name 

163 

164 previous_via_name = contact.name 

165 else: 

166 previous_via_name = None 

167 

168 all_layer_names = list(layer_regions_by_name.keys()) 

169 

170 # ------------------------------------------------------------------------ 

171 if self.pex_mode.need_capacitance(): 

172 overlap_extractor = OverlapExtractor( 

173 all_layer_names=all_layer_names, 

174 layer_regions_by_name=layer_regions_by_name, 

175 dbu=dbu, 

176 tech_info=self.tech_info, 

177 results=results, 

178 report=report 

179 ) 

180 overlap_extractor.extract() 

181 

182 sidewall_and_fringe_extractor = SidewallAndFringeExtractor( 

183 all_layer_names=all_layer_names, 

184 layer_regions_by_name=layer_regions_by_name, 

185 dbu=dbu, 

186 scale_ratio_to_fit_halo=self.scale_ratio_to_fit_halo, 

187 tech_info=self.tech_info, 

188 results=results, 

189 report=report 

190 ) 

191 sidewall_and_fringe_extractor.extract() 

192 

193 # ------------------------------------------------------------------------ 

194 if self.pex_mode.need_resistance(): 

195 c: kdb.Circuit = netlist.top_circuit() 

196 info(f"LVSDB: found {c.pin_count()}pins") 

197 

198 # FIXME: 

199 # currenly, tesselation does not work: 

200 # https://github.com/KLayout/klayout/issues/2100 

201 r_extractor = RExtractor(pex_context=self.pex_context, 

202 substrate_algorithm=pb_RExtractorTech.Algorithm.ALGORITHM_SQUARE_COUNTING, 

203 #substrate_algorithm = pb_RExtractorTech.Algorithm.ALGORITHM_TESSELATION, 

204 wire_algorithm = pb_RExtractorTech.Algorithm.ALGORITHM_SQUARE_COUNTING, 

205 delaunay_b = self.delaunay_b, 

206 delaunay_amax = self.delaunay_amax, 

207 via_merge_distance = 0, 

208 skip_simplify = True) 

209 rex_request = r_extractor.prepare_request() 

210 report.output_rex_request(request=rex_request) 

211 

212 rex_result = r_extractor.extract(rex_request) 

213 report.output_rex_result(result=rex_result) 

214 

215 # 

216 # node_by_id: Dict[int, r_network_pb2.RNode] = {} 

217 # subproc("\tNodes:") 

218 # for node in rex_result.nodes: 

219 # node_by_id[node.node_id] = node 

220 # 

221 # msg = f"\t\tNode #{hex(node.node_id)} '{node.node_name}' " \ 

222 # f"of net '{node.net_name}' " \ 

223 # f"on layer '{node.layer_name}' " 

224 # match node.location.kind: 

225 # case location_pb2.Location.Kind.LOCATION_KIND_POINT: 

226 # p = node.location.point 

227 # msg += f"at {p.x},{p.y} ({p.x * dbu} µm, {p.y * dbu} µm)" 

228 # case location_pb2.Location.Kind.LOCATION_KIND_BOX: 

229 # b = node.location.box 

230 # msg += f"at {b.lower_left.x},{b.lower_left.y};{b.upper_right.x},{b.upper_right.y} (" \ 

231 # f"B/L {round(b.lower_left.x * dbu, 3)},"\ 

232 # f"{round(b.lower_left.y * dbu, 3)} µm, " \ 

233 # f"T/R {round(b.upper_right.x * dbu, 3)},"\ 

234 # f"{round(b.upper_right.y * dbu)} µm)" 

235 # subproc(msg) 

236 # 

237 # subproc("\tElements:") 

238 # for element in rex_result.elements: 

239 # node_a = node_by_id[element.node_a.node_id] 

240 # node_b = node_by_id[element.node_b.node_id] 

241 # subproc(f"\t\t{node_a.node_name} (port net '{node_a.net_name}') " 

242 # f"↔︎ {node_b.node_name} (port net '{node_b.net_name}') " 

243 # f"{round(element.resistance, 3)} Ω") 

244 

245 results.r_extraction_result = rex_result 

246 

247 return results