Coverage for tests/rcx25/rcx25_test.py: 100%

109 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 io 

27import json 

28import tempfile 

29 

30import allure 

31import csv_diff 

32import os 

33import pytest 

34from typing import * 

35 

36import klayout.db as kdb 

37import klayout.lay as klay 

38 

39from klayout_pex.kpex_cli import KpexCLI 

40from klayout_pex.rcx25.extraction_results import CellExtractionResults 

41 

42 

43CSVPath = str 

44PNGPath = str 

45parent_suite = "kpex/2.5D Extraction Tests" 

46tags = ("PEX", "2.5D", "MAGIC") 

47 

48 

49def _kpex_pdk_dir() -> str: 

50 return os.path.realpath(os.path.join(__file__, '..', '..', '..', 

51 'pdk', 'sky130A', 'libs.tech', 'kpex')) 

52 

53 

54def _sky130a_testdata_dir() -> str: 

55 return os.path.realpath(os.path.join(__file__, '..', '..', '..', 

56 'testdata', 'designs', 'sky130A')) 

57 

58 

59def _gds(*path_components) -> str: 

60 return os.path.join(_sky130a_testdata_dir(), *path_components) 

61 

62 

63def _save_layout_preview(gds_path: str, 

64 output_png_path: str): 

65 kdb.Technology.clear_technologies() 

66 default_lyt_path = os.path.abspath(f"{_kpex_pdk_dir()}/sky130A.lyt") 

67 tech = kdb.Technology.create_technology('sky130A') 

68 tech.load(default_lyt_path) 

69 

70 lv = klay.LayoutView() 

71 lv.load_layout(gds_path) 

72 lv.max_hier() 

73 lv.set_config('background-color', '#000000') 

74 lv.set_config('bitmap-oversampling', '1') 

75 lv.set_config('default-font-size', '4') 

76 lv.set_config('default-text-size', '0.1') 

77 lv.save_image_with_options( 

78 output_png_path, 

79 width=4096, height=2160 

80 # , 

81 # linewidth=2, 

82 # resolution=0.25 # 4x as large fonts 

83 ) 

84 

85def _run_rcx25d_single_cell(*path_components) -> Tuple[CellExtractionResults, CSVPath, PNGPath]: 

86 gds_path = _gds(*path_components) 

87 

88 preview_png_path = tempfile.mktemp(prefix=f"layout_preview_", suffix=".png") 

89 _save_layout_preview(gds_path, preview_png_path) 

90 output_dir_path = os.path.realpath(os.path.join(__file__, '..', '..', '..', 'output_sky130A')) 

91 cli = KpexCLI() 

92 cli.main(['main', 

93 '--pdk', 'sky130A', 

94 '--gds', gds_path, 

95 '--out_dir', output_dir_path, 

96 '--2.5D', 'y']) 

97 assert cli.rcx25_extraction_results is not None 

98 assert len(cli.rcx25_extraction_results.cell_extraction_results) == 1 # assume single cell test 

99 results = list(cli.rcx25_extraction_results.cell_extraction_results.values())[0] 

100 assert results.cell_name == path_components[-1][:-len('.gds.gz')] 

101 return results, cli.rcx25_extracted_csv_path, preview_png_path 

102 

103 

104def assert_expected_matches_obtained(*path_components, 

105 expected_csv_content: str) -> CellExtractionResults: 

106 result, csv, preview_png = _run_rcx25d_single_cell(*path_components) 

107 allure.attach.file(csv, name='pex_obtained.csv', attachment_type=allure.attachment_type.CSV) 

108 allure.attach.file(preview_png, name='📸 layout_preview.png', attachment_type=allure.attachment_type.PNG) 

109 expected_csv = csv_diff.load_csv(io.StringIO(expected_csv_content), key='Device') 

110 with open(csv, 'r') as f: 

111 obtained_csv = csv_diff.load_csv(f, key='Device') 

112 diff = csv_diff.compare(expected_csv, obtained_csv, show_unchanged=False) 

113 human_diff = csv_diff.human_text( 

114 diff, current=obtained_csv, extras=(('Net1','{Net1}'),('Net2','{Net2}')) 

115 ) 

116 allure.attach(expected_csv_content, name='pex_expected.csv', attachment_type=allure.attachment_type.CSV) 

117 allure.attach(json.dumps(diff, sort_keys=True, indent=' ').encode("utf8"), 

118 name='pex_diff.json', attachment_type=allure.attachment_type.JSON) 

119 allure.attach(human_diff.encode("utf8"), name='‼️ pex_diff.txt', attachment_type=allure.attachment_type.TEXT) 

120 # assert diff['added'] == [] 

121 # assert diff['removed'] == [] 

122 # assert diff['changed'] == [] 

123 # assert diff['columns_added'] == [] 

124 # assert diff['columns_removed'] == [] 

125 assert human_diff == '', 'Diff detected' 

126 return result 

127 

128@allure.parent_suite(parent_suite) 

129@allure.tag(*tags) 

130@pytest.mark.slow 

131def test_single_plate_100um_x_100um_li1_over_substrate(): 

132 # MAGIC GIVES (8.3 revision 485): 

133 #_______________________________ NOTE: with halo=8µm __________________________________ 

134 # C0 PLATE VSUBS 0.38618p 

135 assert_expected_matches_obtained( 

136 'test_patterns', 'single_plate_100um_x_100um_li1_over_substrate.gds.gz', 

137 expected_csv_content="""Device;Net1;Net2;Capacitance [fF] 

138C1;PLATE;VSUBS;386.18""" 

139 ) 

140 

141 

142@allure.parent_suite(parent_suite) 

143@allure.tag(*tags) 

144@pytest.mark.slow 

145@pytest.mark.wip 

146def test_overlap_plates_100um_x_100um_li1_m1(): 

147 # MAGIC GIVES (8.3 revision 485): 

148 #_______________________________ NOTE: with halo=8µm __________________________________ 

149 # C2 LOWER VSUBS 0.38618p 

150 # C0 UPPER LOWER 0.294756p 

151 # C1 UPPER VSUBS 0.205833p 

152 #_______________________________ NOTE: with halo=50µm __________________________________ 

153 # C2 LOWER VSUBS 0.38618p 

154 # C0 LOWER UPPER 0.294867p 

155 # C1 UPPER VSUBS 0.205621p 

156 # NOTE: magic with --magic_halo=50 (µm) gives UPPER-VSUBS of 0.205621p 

157 # which is due to the handling of https://github.com/martinjankoehler/magic/issues/1 

158 assert_expected_matches_obtained( 

159 'test_patterns', 'overlap_plates_100um_x_100um_li1_m1.gds.gz', 

160 expected_csv_content="""Device;Net1;Net2;Capacitance [fF] 

161C1;LOWER;VSUBS;386.18 

162C2;LOWER;UPPER;294.592 

163C3;UPPER;VSUBS;205.52""" 

164 ) 

165 

166@allure.parent_suite(parent_suite) 

167@allure.tag(*tags) 

168@pytest.mark.slow 

169@pytest.mark.wip 

170def test_overlap_plates_100um_x_100um_li1_m1_m2_m3(): 

171 # MAGIC GIVES (8.3 revision 485): (sorting changed to match order) 

172 #_______________________________ NOTE: with halo=8µm __________________________________ 

173 # C7 li1 VSUBS 0.38618p 

174 # C3 li1 met1 0.294756p # DIFFERS a bit !!! TODO 

175 # C6 met1 VSUBS 0.205833p # DIFFERS a bit !!! TODO 

176 # C0 met1 met2 0.680652p # DIFFERS a bit !!! TODO 

177 # C2 li1 met2 99.3128f # DIFFERS a bit !!! TODO 

178 # C5 met2 VSUBS 52.151802f 

179 # C4 met3 VSUBS 0.136643p 

180 # C1 li1 met3 5.59194f 

181 #_______________________________ NOTE: with halo=50µm __________________________________ 

182 # C9 li1 VSUBS 0.38618p 

183 # C5 li1 met1 0.294867p # DIFFERS a bit !!! TODO 

184 # C8 met1 VSUBS 0.205621p # DIFFERS, but that's a MAGIC issue (see test_overlap_plates_100um_x_100um_li1_m1) 

185 # C2 met1 met2 0.680769p 

186 # C4 li1 met2 99.518005f # DIFFERS a bit !!! TODO 

187 # C7 met2 VSUBS 51.5767f # DIFFERS a bit !!! TODO 

188 # C3 li1 met3 6.01281f # DIFFERS !!! TODO 

189 # C0 met2 met3 0.0422f # we don't have that?! !!! TODO 

190 # C6 met3 VSUBS 0.136103p # DIFFERS a bit !!! TODO 

191 # C1 met1 met3 0.012287f # NOTE: we don't have that, due to halo=8µm 

192 

193 assert_expected_matches_obtained( 

194 'test_patterns', 'overlap_plates_100um_x_100um_li1_m1_m2_m3.gds.gz', 

195 expected_csv_content="""Device;Net1;Net2;Capacitance [fF] 

196C1;VSUBS;li1;386.18 

197C2;li1;met1;294.592 

198C3;VSUBS;met1;205.52 

199C4;met1;met2;680.482 

200C5;li1;met2;99.015 

201C6;VSUBS;met2;51.302 

202C7;VSUBS;met3;135.996 

203C8;li1;met3;5.031""" 

204 ) 

205 

206 

207@allure.parent_suite(parent_suite) 

208@allure.tag(*tags) 

209@pytest.mark.slow 

210@pytest.mark.wip 

211def test_sidewall_100um_x_100um_distance_200nm_li1(): 

212 # MAGIC GIVES (8.3 revision 485): (sorting changed to match order) 

213 # _______________________________ NOTE: with halo=8µm __________________________________ 

214 # C2 C VSUBS 8.231f 

215 # C4 A VSUBS 8.231f 

216 # C3 B VSUBS 4.54159f 

217 # C0 B C 7.5f 

218 # C1 A B 7.5f 

219 # _______________________________ NOTE: with halo=50µm __________________________________ 

220 # (same!) 

221 

222 assert_expected_matches_obtained( 

223 'test_patterns', 'sidewall_100um_x_100um_distance_200nm_li1.gds.gz', 

224 expected_csv_content="""Device;Net1;Net2;Capacitance [fF] 

225C1;C;VSUBS;11.92 # TODO: magic=8.231f  

226C2;A;VSUBS;11.92 # TODO: magic=8.231f 

227C3;B;VSUBS;11.92 # TODO: magic=4.452f 

228C4;B;C;7.5 

229C5;A;B;7.5""" 

230 ) 

231 

232 

233@allure.parent_suite(parent_suite) 

234@allure.tag(*tags) 

235@pytest.mark.slow 

236@pytest.mark.wip 

237def test_sidewall_net_uturn_l1_redux(): 

238 # MAGIC GIVES (8.3 revision 485): (sorting changed to match order) 

239 # _______________________________ NOTE: with halo=8µm __________________________________ 

240 # C2 C0 VSUBS 38.1255f 

241 # C1 C1 VSUBS 12.5876f 

242 # C0 C0 C1 1.87386f 

243 # _______________________________ NOTE: with halo=50µm __________________________________ 

244 # (same!) 

245 

246 assert_expected_matches_obtained( 

247 'test_patterns', 'sidewall_net_uturn_l1_redux.gds.gz', 

248 expected_csv_content="""Device;Net1;Net2;Capacitance [fF] 

249C1;C0;VSUBS;40.642 

250C2;C1;VSUBS;15.079 

251C3;C0;C1;0.019 TODO, MAGIC=1.87386 fF""" 

252 ) 

253 

254 

255@allure.parent_suite(parent_suite) 

256@allure.tag(*tags) 

257@pytest.mark.slow 

258@pytest.mark.wip 

259def test_sidewall_cap_vpp_04p4x04p6_l1_redux(): 

260 # MAGIC GIVES (8.3 revision 485): (sorting changed to match order) 

261 # _______________________________ NOTE: with halo=8µm __________________________________ 

262 # C2 C0 VSUBS 0.300359f 

263 # C1 C1 VSUBS 0.086832f 

264 # C0 C0 C1 0.286226f 

265 # _______________________________ NOTE: with halo=50µm __________________________________ 

266 # (same!) 

267 

268 assert_expected_matches_obtained( 

269 'test_patterns', 'sidewall_cap_vpp_04p4x04p6_l1_redux.gds.gz', 

270 expected_csv_content="""Device;Net1;Net2;Capacitance [fF] 

271C1;C0;VSUBS;0.447 TODO 

272C2;C1;VSUBS;0.223 TODO 

273C3;C0;C1;0.145 TODO""" 

274 ) 

275 

276 

277@allure.parent_suite(parent_suite) 

278@allure.tag(*tags) 

279@pytest.mark.slow 

280@pytest.mark.wip 

281def test_near_body_shield_li1_m1(): 

282 # MAGIC GIVES (8.3 revision 485): (sorting changed to match order) 

283 #_______________________________ NOTE: with halo=8µm __________________________________ 

284 # C5 BOTTOM VSUBS 0.405082p 

285 # C1 BOTTOM TOPB 0.215823p # DIFFERS marginally <0,1fF 

286 # C2 BOTTOM TOPA 0.215823p # DIFFERS marginally <0,1fF 

287 # C0 TOPA TOPB 0.502857f 

288 # C3 TOPB VSUBS 0.737292f # DIFFERS, but that's a MAGIC issue (see test_overlap_plates_100um_x_100um_li1_m1) 

289 # C4 TOPA VSUBS 0.737292f # DIFFERS, but that's a MAGIC issue (see test_overlap_plates_100um_x_100um_li1_m1) 

290 #_______________________________ NOTE: with halo=50µm __________________________________ 

291 # NOTE: with halo=50µm, C3/C4 becomes 0.29976f 

292 # see https://github.com/martinjankoehler/magic/issues/2 

293 

294 assert_expected_matches_obtained( 

295 'test_patterns', 'near_body_shield_li1_m1.gds.gz', 

296 expected_csv_content="""Device;Net1;Net2;Capacitance [fF] 

297C1;BOTTOM;VSUBS;405.082 

298C2;BOTTOM;TOPA;215.898 

299C3;BOTTOM;TOPB;215.898 

300C4;TOPA;TOPB;0.503""" 

301 ) 

302 

303 

304@allure.parent_suite(parent_suite) 

305@allure.tag(*tags) 

306@pytest.mark.slow 

307@pytest.mark.wip 

308def test_sideoverlap_simple_plates_li1_m1(): 

309 # MAGIC GIVES (8.3 revision 485): (sorting changed to match order) 

310 # _______________________________ NOTE: with halo=8µm __________________________________ 

311 # C2 li1 VSUBS 7.931799f 

312 # C1 met1 VSUBS 0.248901p 

313 # C0 li1 met1 0.143335f 

314 # _______________________________ NOTE: with halo=50µm __________________________________ 

315 # C2 li1 VSUBS 7.931799f 

316 # C1 met1 VSUBS 0.248901p 

317 # C0 li1 met1 0.156859f 

318 

319 assert_expected_matches_obtained( 

320 'test_patterns', 'sideoverlap_simple_plates_li1_m1.gds.gz', 

321 expected_csv_content="""Device;Net1;Net2;Capacitance [fF] 

322C1;VSUBS;li1;7.932 

323C2;VSUBS;met1;249.059 

324C3;li1;met1;0.125 TODO""" 

325 )