Coverage for klayout_pex/rcx25/r/resistor_extraction.py: 93%

73 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-03-31 19:36 +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# 

24 

25from __future__ import annotations 

26from typing import * 

27 

28import klayout.db as kdb 

29 

30from klayout_pex.log import ( 

31 debug, 

32 error, 

33 warning 

34) 

35from .conductance import Conductance 

36from .resistor_network import ResistorNetwork, ResistorNetworks 

37 

38class ResistorExtraction: 

39 """ 

40 Provides a resistor extraction 

41 

42 This object translates a set of polygons and pins with labels into 

43 resistor networks. 

44 """ 

45 

46 def __init__(self, 

47 b: float = 0.0, 

48 amax: float = 0.0): 

49 """ 

50 Constructor 

51 

52 :param b: the "b" parameter governing the minimum angle in the Delaunay triangulation 

53 :param amax: the "amax" parameter governing the maxium area (in square database units) during Delaunay triangulation 

54 """ 

55 self.b = b 

56 self.amax = amax 

57 

58 def extract(self, 

59 polygons: kdb.Region, 

60 pins: kdb.Region, 

61 labels: kdb.Texts, 

62 doc_layer: kdb.Shapes = None) -> List[ResistorNetwork]: 

63 """ 

64 Extracts resistor networks from the polygons on the given regions 

65 

66 The conductance values are normalized to a conductivitiy of 1 Ohm/square. 

67 

68 :param polygons: the resistor shape polygons 

69 :param pins: the pin shapes - every pin shape is considered infinitely conductive 

70 :param labels: the texts labelling the pins 

71 :param doc_layer: an option kdb.Shapes object where some documentation is provided 

72 """ 

73 

74 networks = [] 

75 

76 for p in polygons.merged(): 

77 debug(f"Working on polygon {str(p)} ...") 

78 

79 pin_shapes = {} 

80 

81 r = kdb.Region(p) 

82 il = labels.interacting(r + pins.interacting(r)) 

83 

84 pr = kdb.Region() 

85 

86 for l in il: 

87 pins_for_label = pins.interacting(kdb.Texts(l)) 

88 for pp in pins_for_label: 

89 if l.string not in pin_shapes: 

90 pin_shapes[l.string] = [] 

91 pin_shapes[l.string].append(pp) 

92 pr.insert(pp) 

93 

94 r -= pr 

95 

96 try: 

97 tris = r.delaunay(self.amax, self.b) 

98 except Exception as e: 

99 error(f"Failed to perform delaunay triangulation (a={self.amax}, b={self.b}) " 

100 f"on polygon {r} due to exception: {e}") 

101 continue 

102 debug(f"Decomposed into {tris.count()} triangles with b={self.b} and amax={self.amax}") 

103 

104 rn = ResistorNetwork() 

105 

106 for t in tris.each(): 

107 a = t.area() 

108 

109 for pp in [t]: 

110 if doc_layer is not None: 

111 doc_layer.insert(pp) 

112 

113 pts = [pt for pt in pp.each_point_hull()] 

114 

115 for i in range(0, 3): 

116 pm1 = pts[i] 

117 p0 = pts[(i + 1) % 3] 

118 p1 = pts[(i + 2) % 3] 

119 

120 lm1 = (p0 - pm1).sq_length() 

121 l0 = (p1 - p0).sq_length() 

122 l1 = (pm1 - p1).sq_length() 

123 

124 s = (l0 + l1 - lm1) / (8.0 * a) 

125 

126 nid0 = rn.node_id(p0) 

127 nidm1 = rn.node_id(pm1) 

128 

129 rn.add_cond(nid0, nidm1, Conductance(s)) 

130 d = pm1 - p0 

131 lpos = p0 + d * 0.5 + kdb.Vector(-d.y, d.x) / d.length() 

132 

133 if doc_layer is not None: 

134 doc_layer.insert(kdb.Text("%.6g" % s, kdb.Trans(lpos))) 

135 

136 # connects all nodes on the pin edge 

137 

138 for label in sorted(pin_shapes.keys()): 

139 for poly in pin_shapes[label]: 

140 nid0 = None 

141 num = 1 

142 for e in poly.each_edge(): 

143 for nid in rn.node_ids(e): 

144 if nid0 is None: 

145 nid0 = nid 

146 elif nid0 != nid: 

147 rn.connect_nodes(nid0, nid) 

148 num += 1 

149 if nid0 is not None: 

150 debug(f"Using {label} for node {nid0} ({num} connected nodes)") 

151 rn.name(nid0, label) 

152 rn.mark_precious(nid0) 

153 

154 # does a self-check 

155 rn.check() 

156 

157 # eliminates internal nodes 

158 rn.eliminate_all() 

159 

160 networks.append(rn) 

161 

162 return networks