Coverage for klayout_pex/rcx25/extraction_reporter.py: 78%

106 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 functools import cached_property 

26 

27import klayout.rdb as rdb 

28import klayout.db as kdb 

29 

30from .extraction_results import * 

31from klayout_pex.rcx25.c.geometry_restorer import GeometryRestorer 

32from .types import EdgeNeighborhood, LayerName 

33from ..klayout.lvsdb_extractor import KLayoutDeviceInfo 

34 

35VarShapes = kdb.Shapes | kdb.Region | List[kdb.Edge] | List[kdb.Polygon] 

36 

37 

38class ExtractionReporter: 

39 def __init__(self, 

40 cell_name: str, 

41 dbu: float): 

42 self.report = rdb.ReportDatabase(f"PEX {cell_name}") 

43 self.cell = self.report.create_cell(cell_name) 

44 self.dbu_trans = kdb.CplxTrans(mag=dbu) 

45 self.category_name_counter: Dict[str, int] = defaultdict(int) 

46 

47 @cached_property 

48 def cat_common(self) -> rdb.RdbCategory: 

49 return self.report.create_category('Common') 

50 

51 @cached_property 

52 def cat_pins(self) -> rdb.RdbCategory: 

53 return self.report.create_category("Pins") 

54 

55 @cached_property 

56 def cat_devices(self) -> rdb.RdbCategory: 

57 return self.report.create_category("Devices") 

58 

59 @cached_property 

60 def cat_vias(self) -> rdb.RdbCategory: 

61 return self.report.create_category("Vias") 

62 

63 @cached_property 

64 def cat_overlap(self) -> rdb.RdbCategory: 

65 return self.report.create_category("[C] Overlap") 

66 

67 @cached_property 

68 def cat_sidewall(self) -> rdb.RdbCategory: 

69 return self.report.create_category("[C] Sidewall") 

70 

71 @cached_property 

72 def cat_fringe(self) -> rdb.RdbCategory: 

73 return self.report.create_category("[C] Fringe / Side Overlap") 

74 

75 @cached_property 

76 def cat_edge_neighborhood(self) -> rdb.RdbCategory: 

77 return self.report.create_category("[C] Edge Neighborhood Visitor") 

78 

79 def save(self, path: str): 

80 self.report.save(path) 

81 

82 def output_shapes(self, 

83 parent_category: rdb.RdbCategory, 

84 category_name: str, 

85 shapes: VarShapes) -> rdb.RdbCategory: 

86 rdb_cat = self.report.create_category(parent_category, category_name) 

87 self.report.create_items(self.cell.rdb_id(), ## TODO: if later hierarchical mode is introduced 

88 rdb_cat.rdb_id(), 

89 self.dbu_trans, 

90 shapes) 

91 return rdb_cat 

92 

93 def output_overlap(self, 

94 overlap_cap: OverlapCap, 

95 bottom_polygon: kdb.PolygonWithProperties, 

96 top_polygon: kdb.PolygonWithProperties, 

97 overlap_area: kdb.Region): 

98 cat_overlap_top_layer = self.report.create_category(self.cat_overlap, 

99 f"top_layer={overlap_cap.key.layer_top}") 

100 cat_overlap_bot_layer = self.report.create_category(cat_overlap_top_layer, 

101 f'bot_layer={overlap_cap.key.layer_bot}') 

102 cat_overlap_nets = self.report.create_category(cat_overlap_bot_layer, 

103 f'{overlap_cap.key.net_top}{overlap_cap.key.net_bot}') 

104 self.category_name_counter[cat_overlap_nets.path()] += 1 

105 cat_overlap_cap = self.report.create_category( 

106 cat_overlap_nets, 

107 f"#{self.category_name_counter[cat_overlap_nets.path()]} " 

108 f"{round(overlap_cap.cap_value, 3)} fF", 

109 ) 

110 

111 self.output_shapes(cat_overlap_cap, "Top Polygon", [top_polygon]) 

112 self.output_shapes(cat_overlap_cap, "Bottom Polygon", [bottom_polygon]) 

113 self.output_shapes(cat_overlap_cap, "Overlap Area", overlap_area) 

114 

115 def output_sidewall(self, 

116 sidewall_cap: SidewallCap, 

117 inside_edge: kdb.Edge, 

118 outside_edge: kdb.Edge): 

119 cat_sidewall_layer = self.report.create_category(self.cat_sidewall, 

120 f"layer={sidewall_cap.key.layer}") 

121 cat_sidewall_net_inside = self.report.create_category(cat_sidewall_layer, 

122 f'inside={sidewall_cap.key.net1}') 

123 cat_sidewall_net_outside = self.report.create_category(cat_sidewall_net_inside, 

124 f'outside={sidewall_cap.key.net2}') 

125 self.category_name_counter[cat_sidewall_net_outside.path()] += 1 

126 

127 self.output_shapes( 

128 cat_sidewall_net_outside, 

129 f"#{self.category_name_counter[cat_sidewall_net_outside.path()]}: " 

130 f"len {sidewall_cap.length} µm, " 

131 f"distance {sidewall_cap.distance} µm, " 

132 f"{round(sidewall_cap.cap_value, 3)} fF", 

133 [inside_edge, outside_edge] 

134 ) 

135 

136 def output_sideoverlap(self, 

137 sideoverlap_cap: SideOverlapCap, 

138 inside_edge: kdb.Edge, 

139 outside_polygon: kdb.Polygon, 

140 lateral_shield: Optional[kdb.Region]): 

141 cat_sideoverlap_layer_inside = self.report.create_category(self.cat_fringe, 

142 f"inside_layer={sideoverlap_cap.key.layer_inside}") 

143 cat_sideoverlap_net_inside = self.report.create_category(cat_sideoverlap_layer_inside, 

144 f'inside_net={sideoverlap_cap.key.net_inside}') 

145 cat_sideoverlap_layer_outside = self.report.create_category(cat_sideoverlap_net_inside, 

146 f'outside_layer={sideoverlap_cap.key.layer_outside}') 

147 cat_sideoverlap_net_outside = self.report.create_category(cat_sideoverlap_layer_outside, 

148 f'outside_net={sideoverlap_cap.key.net_outside}') 

149 self.category_name_counter[cat_sideoverlap_net_outside.path()] += 1 

150 

151 cat_sideoverlap_cap = self.report.create_category( 

152 cat_sideoverlap_net_outside, 

153 f"#{self.category_name_counter[cat_sideoverlap_net_outside.path()]}: " 

154 f"{round(sideoverlap_cap.cap_value, 3)} fF" 

155 ) 

156 

157 self.output_shapes(cat_sideoverlap_cap, 'Inside Edge', inside_edge) 

158 

159 shapes = kdb.Shapes() 

160 shapes.insert(outside_polygon) 

161 self.output_shapes(cat_sideoverlap_cap, 'Outside Polygon', shapes) 

162 

163 if lateral_shield is not None: 

164 self.output_shapes(cat_sideoverlap_cap, 'Lateral Shield', 

165 [lateral_shield]) 

166 

167 def output_edge_neighborhood(self, 

168 inside_layer: LayerName, 

169 all_layer_names: List[LayerName], 

170 edge: kdb.EdgeWithProperties, 

171 neighborhood: EdgeNeighborhood, 

172 geometry_restorer: GeometryRestorer): 

173 cat_en_layer_inside = self.report.create_category(self.cat_edge_neighborhood, f"inside_layer={inside_layer}") 

174 inside_net = edge.property('net') 

175 cat_en_net_inside = self.report.create_category(cat_en_layer_inside, f'inside_net={inside_net}') 

176 

177 for edge_interval, polygons_by_child in neighborhood: 

178 cat_en_edge_interval = self.report.create_category(cat_en_net_inside, f"Edge Interval: {edge_interval}") 

179 self.category_name_counter[cat_en_edge_interval.path()] += 1 

180 cat_en_edge = self.report.create_category( 

181 cat_en_edge_interval, 

182 f"#{self.category_name_counter[cat_en_edge_interval.path()]}" 

183 ) 

184 self.output_shapes(cat_en_edge, "Edge", [edge]) # geometry_restorer.restore_edge(edge)) 

185 

186 for child_index, polygons in polygons_by_child.items(): 

187 self.output_shapes( 

188 cat_en_edge, 

189 f"Child {child_index}: " 

190 f"{child_index < len(all_layer_names) and all_layer_names[child_index] or 'None'}", 

191 [geometry_restorer.restore_polygon(p) for p in polygons] 

192 ) 

193 

194 def output_devices(self, 

195 devices_by_name: Dict[str, KLayoutDeviceInfo]): 

196 for d in devices_by_name.values(): 

197 self.output_device(d) 

198 

199 def output_device(self, 

200 device: KLayoutDeviceInfo): 

201 cat_device = self.report.create_category( 

202 self.cat_devices, 

203 f"{device.name}: {device.class_name}" 

204 ) 

205 cat_device_params = self.report.create_category(cat_device, 'Params') 

206 for name, value in device.params.items(): 

207 self.report.create_category(cat_device_params, f"{name}: {value}") 

208 

209 cat_device_terminals = self.report.create_category(cat_device, 'Terminals') 

210 for t in device.terminals.terminals: 

211 if t.regions_by_layer_name: 

212 for layer_name, regions in t.regions_by_layer_name.items(): 

213 self.output_shapes( 

214 cat_device_terminals, 

215 f"{t.name}: net {t.net_name}, layer {layer_name}", 

216 regions 

217 ) 

218 else: 

219 self.report.create_category( 

220 cat_device_terminals, 

221 f"{t.name}: net <NOT CONNECTED> (TODO layer/shapes)", 

222 ) 

223 

224 def output_via(self, 

225 via_name: LayerName, 

226 bottom_layer: LayerName, 

227 top_layer: LayerName, 

228 net: str, 

229 via_width: float, 

230 via_spacing: float, 

231 via_border: float, 

232 polygon: kdb.Polygon, 

233 ohm: float, 

234 comment: str): 

235 cat_via_layers = self.report.create_category( 

236 self.cat_vias, 

237 f"{via_name} ({bottom_layer}{top_layer}) (w={via_width}, sp={via_spacing}, b={via_border})" 

238 ) 

239 

240 self.category_name_counter[cat_via_layers.path()] += 1 

241 

242 self.output_shapes( 

243 cat_via_layers, 

244 f"#{self.category_name_counter[cat_via_layers.path()]} " 

245 f"{ohm} Ω (net {net}) | {comment}", 

246 [polygon] 

247 ) 

248 

249 def output_pin(self, 

250 layer_name: LayerName, 

251 pin_point: kdb.Box, 

252 label: kdb.Text): 

253 cat_pin_layer = self.report.create_category(self.cat_pins, layer_name) 

254 sh = kdb.Shapes() 

255 sh.insert(pin_point) 

256 self.output_shapes(cat_pin_layer, label.string, sh)