Coverage for klayout_pex/rcx25/r/r_extractor.py: 91%

255 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-31 20:14 +0000

1# 

2# -------------------------------------------------------------------------------- 

3# SPDX-FileCopyrightText: 2024-2025 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/iic-jku/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# 

24 

25from collections import defaultdict 

26from typing import * 

27 

28from klayout_pex.log import ( 

29 warning, 

30 subproc, 

31) 

32 

33from ..types import NetName 

34 

35from klayout_pex.klayout.shapes_pb2_converter import ShapesConverter 

36from klayout_pex.klayout.lvsdb_extractor import KLayoutExtractionContext 

37from klayout_pex.klayout.rex_core import klayout_r_extractor_tech 

38 

39import klayout_pex_protobuf.kpex.layout.device_pb2 as device_pb2 

40import klayout_pex_protobuf.kpex.layout.location_pb2 as location_pb2 

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

42import klayout_pex_protobuf.kpex.tech.tech_pb2 as tech_pb2 

43import klayout_pex_protobuf.kpex.r.r_network_pb2 as r_network_pb2 

44import klayout_pex_protobuf.kpex.request.pex_request_pb2 as pex_request_pb2 

45import klayout_pex_protobuf.kpex.result.pex_result_pb2 as pex_result_pb2 

46 

47import klayout.db as kdb 

48import klayout.pex as klp 

49 

50 

51class RExtractor: 

52 def __init__(self, 

53 pex_context: KLayoutExtractionContext, 

54 substrate_algorithm: pb_RExtractorTech.Algorithm, 

55 wire_algorithm: pb_RExtractorTech.Algorithm, 

56 delaunay_b: float, 

57 delaunay_amax: float, 

58 via_merge_distance: float, 

59 skip_simplify: bool): 

60 """ 

61 :param pex_context: KLayout PEX extraction context 

62 :param substrate_algorithm: The KLayout PEXCore Algorithm for decomposing polygons. 

63 Either SquareCounting or Tesselation (recommended) 

64 :param wire_algorithm: The KLayout PEXCore Algorithm for decomposing polygons. 

65 Either SquareCounting (recommended) or Tesselation 

66 :param delaunay_b: The "b" parameter for the Delaunay triangulation, 

67 a ratio of shortest triangle edge to circle radius 

68 :param delaunay_amax: The "max_area" specifies the maximum area of the triangles 

69 produced in square micrometers. 

70 :param via_merge_distance: Maximum distance where close vias are merged together 

71 :param skip_simplify: skip simplification of resistor network 

72 """ 

73 self.pex_context = pex_context 

74 self.substrate_algorithm = substrate_algorithm 

75 self.wire_algorithm = wire_algorithm 

76 self.delaunay_b = delaunay_b 

77 self.delaunay_amax = delaunay_amax 

78 self.via_merge_distance = via_merge_distance 

79 self.skip_simplify = skip_simplify 

80 

81 self.shapes_converter = ShapesConverter(dbu=self.pex_context.dbu) 

82 

83 def prepare_r_extractor_tech_pb(self, 

84 rex_tech: pb_RExtractorTech): 

85 """ 

86 Prepare KLayout PEXCore Technology Description based on the KPEX Tech Info data 

87 :param rex_tech: RExtractorTech protobuffer message 

88 """ 

89 

90 rex_tech.skip_simplify = self.skip_simplify 

91 

92 tech = self.pex_context.tech 

93 

94 for gds_pair, li in self.pex_context.extracted_layers.items(): 

95 computed_layer_info = tech.computed_layer_info_by_gds_pair.get(gds_pair, None) 

96 if computed_layer_info is None: 

97 warning(f"ignoring layer {gds_pair}, no computed layer info found in tech info") 

98 continue 

99 

100 canonical_layer_name = tech.canonical_layer_name_by_gds_pair[gds_pair] 

101 

102 LP = tech_pb2.LayerInfo.Purpose 

103 

104 match computed_layer_info.kind: 

105 case tech_pb2.ComputedLayerInfo.Kind.KIND_PIN: 

106 continue 

107 

108 case tech_pb2.ComputedLayerInfo.Kind.KIND_LABEL: 

109 continue 

110 

111 case _: 

112 pass 

113 

114 match computed_layer_info.layer_info.purpose: 

115 case LP.PURPOSE_NWELL: 

116 pass # TODO! 

117 

118 case LP.PURPOSE_N_IMPLANT | LP.PURPOSE_P_IMPLANT: 

119 # device terminals 

120 # - source/drain (e.g. sky130A: nsdm, psdm) 

121 # - bulk (e.g. nwell) 

122 # 

123 # we will consider this only as a pin end-point, there are no wires at all on this layer, 

124 # so the resistance does not matter for PEX 

125 for source_layer in li.source_layers: 

126 cond = rex_tech.conductors.add() 

127 

128 cond.layer.id = self.pex_context.annotated_layout.layer(*source_layer.gds_pair) 

129 cond.layer.canonical_layer_name = canonical_layer_name 

130 cond.layer.lvs_layer_name = source_layer.lvs_layer_name 

131 

132 cond.triangulation_min_b = self.delaunay_b 

133 cond.triangulation_max_area = self.delaunay_amax 

134 

135 cond.algorithm = self.substrate_algorithm 

136 cond.resistance = 0 # see comment above 

137 

138 case LP.PURPOSE_METAL: 

139 if computed_layer_info.kind == tech_pb2.ComputedLayerInfo.Kind.KIND_PIN: 

140 continue 

141 

142 layer_resistance = tech.layer_resistance_by_layer_name.get(canonical_layer_name, None) 

143 for source_layer in li.source_layers: 

144 cond = rex_tech.conductors.add() 

145 

146 cond.layer.id = self.pex_context.annotated_layout.layer(*source_layer.gds_pair) 

147 cond.layer.canonical_layer_name = canonical_layer_name 

148 cond.layer.lvs_layer_name = source_layer.lvs_layer_name 

149 

150 cond.triangulation_min_b = self.delaunay_b 

151 cond.triangulation_max_area = self.delaunay_amax 

152 

153 if canonical_layer_name == tech.internal_substrate_layer_name: 

154 cond.algorithm = self.substrate_algorithm 

155 else: 

156 cond.algorithm = self.wire_algorithm 

157 cond.resistance = self.pex_context.tech.milliohm_to_ohm(layer_resistance.resistance) 

158 

159 case LP.PURPOSE_CONTACT: 

160 for source_layer in li.source_layers: 

161 contact = tech.contact_by_contact_lvs_layer_name.get(source_layer.lvs_layer_name, None) 

162 if contact is None: 

163 warning( 

164 f"ignoring LVS layer {source_layer.lvs_layer_name} (layer {canonical_layer_name}), " 

165 f"no contact found in tech info") 

166 continue 

167 

168 contact_resistance = tech.contact_resistance_by_device_layer_name.get(contact.layer_below, 

169 None) 

170 if contact_resistance is None: 

171 warning( 

172 f"ignoring LVS layer {source_layer.lvs_layer_name} (layer {canonical_layer_name}), " 

173 f"no contact resistance found in tech info") 

174 continue 

175 

176 via = rex_tech.vias.add() 

177 

178 bot_gds_pair = tech.gds_pair(contact.layer_below) 

179 top_gds_pair = tech.gds_pair(contact.metal_above) 

180 

181 via.layer.id = self.pex_context.annotated_layout.layer(*source_layer.gds_pair) 

182 via.layer.canonical_layer_name = canonical_layer_name 

183 via.layer.lvs_layer_name = source_layer.lvs_layer_name 

184 

185 via.bottom_conductor.id = self.pex_context.annotated_layout.layer(*bot_gds_pair) 

186 via.top_conductor.id = self.pex_context.annotated_layout.layer(*top_gds_pair) 

187 

188 via.resistance = self.pex_context.tech.milliohm_by_cnt_to_ohm_by_square_for_contact( 

189 contact=contact, 

190 contact_resistance=contact_resistance 

191 ) 

192 via.merge_distance = self.via_merge_distance 

193 

194 case LP.PURPOSE_VIA: 

195 via_resistance = tech.via_resistance_by_layer_name.get(canonical_layer_name, None) 

196 if via_resistance is None: 

197 warning(f"ignoring layer {canonical_layer_name}, no via resistance found in tech info") 

198 continue 

199 for source_layer in li.source_layers: 

200 bot_top = tech.bottom_and_top_layer_name_by_via_computed_layer_name.get( 

201 source_layer.lvs_layer_name, None) 

202 if bot_top is None: 

203 warning(f"ignoring layer {canonical_layer_name} (LVS {source_layer.lvs_layer_name}), no bottom/top layers found in tech info") 

204 continue 

205 via = rex_tech.vias.add() 

206 

207 (bot, top) = bot_top 

208 bot_gds_pair = tech.gds_pair(bot) 

209 top_gds_pair = tech.gds_pair(top) 

210 

211 via.layer.id = self.pex_context.annotated_layout.layer(*source_layer.gds_pair) 

212 via.layer.canonical_layer_name = canonical_layer_name 

213 via.layer.lvs_layer_name = source_layer.lvs_layer_name 

214 

215 via.bottom_conductor.id = self.pex_context.annotated_layout.layer(*bot_gds_pair) 

216 via.top_conductor.id = self.pex_context.annotated_layout.layer(*top_gds_pair) 

217 

218 contact = self.pex_context.tech.contact_by_contact_lvs_layer_name[ 

219 source_layer.lvs_layer_name] 

220 

221 via.resistance = self.pex_context.tech.milliohm_by_cnt_to_ohm_by_square_for_via( 

222 contact=contact, 

223 via_resistance=via_resistance 

224 ) 

225 

226 via.merge_distance = self.via_merge_distance 

227 

228 return rex_tech 

229 

230 def prepare_request(self) -> pex_request_pb2.RExtractionRequest: 

231 rex_request = pex_request_pb2.RExtractionRequest() 

232 

233 # prepare tech info 

234 self.prepare_r_extractor_tech_pb(rex_tech=rex_request.tech) 

235 

236 # prepare devices 

237 devices_by_name = self.pex_context.devices_by_name 

238 rex_request.devices.MergeFrom(devices_by_name.values()) 

239 

240 # prepare pins 

241 for pin_list in self.pex_context.pins_pb2_by_layer.values(): 

242 rex_request.pins.MergeFrom(pin_list) 

243 

244 net_request_by_name: Dict[NetName, pex_request_pb2.RNetExtractionRequest] = {} 

245 def get_or_create_net_request(net_name: str): 

246 v = net_request_by_name.get(net_name, None) 

247 if not v: 

248 v = rex_request.net_extraction_requests.add() 

249 v.net_name = net_name 

250 net_request_by_name[net_name] = v 

251 return v 

252 

253 for pin in rex_request.pins: 

254 get_or_create_net_request(pin.net_name).pins.add().CopyFrom(pin) 

255 

256 for device in rex_request.devices: 

257 for terminal in device.terminals: 

258 get_or_create_net_request(terminal.net_name).device_terminals.add().CopyFrom(terminal) 

259 

260 netlist = self.pex_context.lvsdb.netlist() 

261 circuit = netlist.circuit_by_name(self.pex_context.annotated_top_cell.name) 

262 # https://www.klayout.de/doc-qt5/code/class_Circuit.html 

263 if not circuit: 

264 circuits = [c.name for c in netlist.each_circuit()] 

265 raise Exception(f"Expected circuit called {self.pex_context.annotated_top_cell.name} in extracted netlist, " 

266 f"only available circuits are: {circuits}") 

267 LK = tech_pb2.ComputedLayerInfo.Kind 

268 for net in circuit.each_net(): 

269 net_name = net.name or f"${net.cluster_id}" 

270 for lvs_gds_pair, lyr_info in self.pex_context.extracted_layers.items(): 

271 for lyr in lyr_info.source_layers: 

272 li = self.pex_context.tech.computed_layer_info_by_gds_pair[lyr.gds_pair] 

273 match li.kind: 

274 case LK.KIND_PIN: 

275 continue # skip 

276 case LK.KIND_REGULAR | LK.KIND_DEVICE_CAPACITOR | LK.KIND_DEVICE_RESISTOR: 

277 r = self.pex_context.shapes_of_net(lyr.gds_pair, net) 

278 if not r: 

279 continue 

280 l2r = get_or_create_net_request(net_name).region_by_layer.add() 

281 l2r.layer.id = self.pex_context.annotated_layout.layer(*lvs_gds_pair) 

282 l2r.layer.canonical_layer_name = self.pex_context.tech.canonical_layer_name_by_gds_pair[lvs_gds_pair] 

283 l2r.layer.lvs_layer_name = lyr.lvs_layer_name 

284 self.shapes_converter.klayout_region_to_pb(r, l2r.region) 

285 case _: 

286 raise NotImplementedError() 

287 

288 return rex_request 

289 

290 def extract(self, rex_request: pex_request_pb2.RExtractionRequest) -> pex_result_pb2.RExtractionResult: 

291 rex_result = pex_result_pb2.RExtractionResult() 

292 

293 rex_tech_kly = klayout_r_extractor_tech(rex_request.tech) 

294 

295 Label = str 

296 LayerName = str 

297 NetName = str 

298 DeviceID = int 

299 TerminalID = int 

300 

301 # dicts keyed by id / klayout_index 

302 layer_names: Dict[int, LayerName] = {} 

303 

304 wire_layer_ids: Set[int] = set() 

305 via_layer_ids: Set[int] = set() 

306 

307 for c in rex_request.tech.conductors: 

308 layer_names[c.layer.id] = c.layer.canonical_layer_name 

309 wire_layer_ids.add(c.layer.id) 

310 

311 for v in rex_request.tech.vias: 

312 layer_names[v.layer.id] = v.layer.canonical_layer_name 

313 via_layer_ids.add(c.layer.id) 

314 

315 for net_extraction_request in rex_request.net_extraction_requests: 

316 vertex_ports: Dict[int, List[kdb.Point]] = defaultdict(list) 

317 polygon_ports: Dict[int, List[kdb.Polygon]] = defaultdict(list) 

318 vertex_port_pins: Dict[int, List[Tuple[Label, NetName]]] = defaultdict(list) 

319 polygon_port_device_terminals: Dict[int, List[device_pb2.Device.Terminal]] = defaultdict(list) 

320 regions: Dict[int, kdb.Region] = defaultdict(kdb.Region) 

321 

322 for t in net_extraction_request.device_terminals: 

323 for l2r in t.region_by_layer: 

324 for sh in l2r.region.shapes: 

325 sh_kly = self.shapes_converter.klayout_shape(sh) 

326 polygon_ports[l2r.layer.id].append(sh_kly) 

327 polygon_port_device_terminals[l2r.layer.id].append(t) 

328 

329 for pin in net_extraction_request.pins: 

330 p = self.shapes_converter.klayout_point(pin.label_point) 

331 vertex_ports[pin.layer.id].append(p) 

332 vertex_port_pins[pin.layer.id].append((pin.label, pin.net_name)) 

333 

334 for l2r in net_extraction_request.region_by_layer: 

335 regions[l2r.layer.id] = self.shapes_converter.klayout_region(l2r.region) 

336 

337 rex = klp.RNetExtractor(self.pex_context.dbu) 

338 resistor_network = rex.extract(rex_tech_kly, 

339 regions, 

340 vertex_ports, 

341 polygon_ports) 

342 

343 result_network = rex_result.networks.add() 

344 result_network.net_name = net_extraction_request.net_name 

345 

346 for rn in resistor_network.each_node(): 

347 node_by_node_id: Dict[int, r_network_pb2.RNode] = {} 

348 

349 loc = rn.location() 

350 layer_id = rn.layer() 

351 canonical_layer_name = layer_names[layer_id] 

352 

353 r_node = result_network.nodes.add() 

354 r_node.node_id = rn.object_id() 

355 r_node.node_name = rn.to_s() 

356 r_node.node_kind = r_network_pb2.RNode.Kind.KIND_UNSPECIFIED # TODO! 

357 r_node.layer_name = canonical_layer_name 

358 

359 match rn.type(): 

360 case klp.RNodeType.VertexPort: # pins! 

361 r_node.location.kind = location_pb2.Location.Kind.LOCATION_KIND_POINT 

362 p = loc.center().to_itype(self.pex_context.dbu) 

363 r_node.location.point.x = p.x 

364 r_node.location.point.y = p.y 

365 case klp.RNodeType.PolygonPort | klp.RNodeType.Internal: 

366 r_node.location.kind = location_pb2.Location.Kind.LOCATION_KIND_BOX 

367 p1 = loc.p1.to_itype(self.pex_context.dbu) 

368 p2 = loc.p2.to_itype(self.pex_context.dbu) 

369 r_node.location.box.lower_left.x = p1.x 

370 r_node.location.box.lower_left.y = p1.y 

371 r_node.location.box.upper_right.x = p2.x 

372 r_node.location.box.upper_right.y = p2.y 

373 case _: 

374 raise NotImplementedError() 

375 

376 match rn.type(): 

377 case klp.RNodeType.VertexPort: 

378 r_node.node_kind = r_network_pb2.RNode.Kind.KIND_PIN 

379 port_idx = rn.port_index() 

380 r_node.node_name, r_node.net_name = vertex_port_pins[rn.layer()][port_idx][0:2] 

381 r_node.location.point.net = r_node.net_name 

382 

383 case klp.RNodeType.PolygonPort: 

384 r_node.node_kind = r_network_pb2.RNode.Kind.KIND_DEVICE_TERMINAL 

385 port_idx = rn.port_index() 

386 nn = polygon_port_device_terminals[rn.layer()][port_idx].net_name 

387 r_node.net_name = f"{result_network.net_name}.{r_node.node_name}" 

388 r_node.location.box.net = r_node.net_name 

389 case klp.RNodeType.Internal: 

390 if rn.layer() in via_layer_ids: 

391 r_node.node_kind = r_network_pb2.RNode.Kind.KIND_VIA_JUNCTION 

392 elif rn.layer() in wire_layer_ids: 

393 r_node.node_kind = r_network_pb2.RNode.Kind.KIND_WIRE_JUNCTION 

394 else: 

395 raise NotImplementedError() 

396 

397 # NOTE: network prefix, as node name is only unique per network 

398 r_node.net_name = f"{result_network.net_name}.{r_node.node_name}" 

399 r_node.location.box.net = r_node.net_name 

400 case _: 

401 raise NotImplementedError() 

402 

403 node_by_node_id[r_node.node_id] = r_node 

404 

405 for el in resistor_network.each_element(): 

406 r_element = result_network.elements.add() 

407 r_element.element_id = el.object_id() 

408 r_element.node_a.node_id = el.a().object_id() 

409 r_element.node_b.node_id = el.b().object_id() 

410 r_element.resistance = el.resistance() 

411 

412 return rex_result 

413