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

59 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-02 23:32 +0000

1# 

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

3# SPDX-FileCopyrightText: 2024-2026 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# 

24from __future__ import annotations 

25 

26import glob 

27import os 

28import tempfile 

29from unittest import mock 

30 

31import allure 

32import pytest 

33 

34from klayout_pex.kpex_cli import KpexCLI 

35from klayout_pex.pdk_config import PDK 

36from klayout_pex.pex25d.diagnostics import ExitCode 

37 

38TEST_DESIGNS_DIR = os.path.realpath(os.path.join(__file__, '..', '..', '..', 'testdata', 'designs')) 

39 

40DESIGNS = [ 

41 (PDK.SKY130A, 'sky130_fd_sc_hd__inv_1/sky130_fd_sc_hd__inv_1.gds.gz'), 

42 (PDK.SKY130A, 'inv/inv.gds.gz'), 

43 (PDK.SKY130A, 'test_patterns/nfet_li1_redux.gds.gz'), 

44 (PDK.SKY130A, 'test_patterns/r_nwell_pfet_ntap_li1.gds.gz'), 

45 (PDK.SKY130A, 'test_patterns/r_nwell_pfet_label_li1.gds.gz'), 

46 (PDK.SKY130A, 'cap_vpp_04p4x04p6_l1m1m2_noshield/cap_vpp_04p4x04p6_l1m1m2_noshield.gds.gz'), 

47 (PDK.GF180MCUD, 'test_patterns/nfet_m1.gds.gz'), 

48 (PDK.GF180MCUD, 'test_patterns/r_nwell_pfet_ntap_m1.gds.gz'), 

49 (PDK.IHP_SG13G2, 'sg13g2_a21o_1/sg13g2_a21o_1.gds.gz'), 

50 (PDK.IHP_SG13G2, 'rfnmos/rfnmos_w1u_l0u72.gds.gz'), 

51 (PDK.IHP_SG13G2, 'cap_vpp_04p4x04p6_m1m2m3_substrate_pin/cap_vpp_04p4x04p6_m1m2m3_substrate_pin.gds.gz'), 

52 (PDK.IHP_SG13G2, 'test_patterns/nmos_metal1_redux_twice.gds.gz'), 

53 (PDK.IHP_SG13G2, 'test_patterns/nmos_metal1_redux_two_drain_labels.gds.gz'), 

54 (PDK.IHP_SG13G2, 'test_patterns/nmos_metal1_redux_two_labels_one_node.gds.gz'), 

55 (PDK.IHP_SG13CMOS5L, 'cap_cmomf_w5u_l5u_m1_m4/cap_cmomf_w5u_l5u_m1_m4.gds.gz'), 

56] 

57 

58 

59EXPECTED_FAILURES = { 

60 ('cap_vpp_04p4x04p6_l1m1m2_noshield/cap_vpp_04p4x04p6_l1m1m2_noshield.gds.gz', 'R'): 

61 "in R mode, the whiteboxed MOM cap leaves its port SUB touching nothing, as there are no capacitances", 

62} 

63 

64 

65@allure.parent_suite("kpex/2.5D Extraction Tests [RC netlist checks]") 

66@allure.tag("PEX", "2.5D") 

67@pytest.mark.slow 

68@pytest.mark.parametrize('mode', ['R', 'RC']) 

69@pytest.mark.parametrize('pdk,gds', DESIGNS) 

70def test_rc_netlist_is_consistent(pdk: PDK, gds: str, mode: str, request: pytest.FixtureRequest): 

71 # NOTE: each defect of #211 §6, §10, §11 fails these checks (#215) 

72 reason = EXPECTED_FAILURES.get((gds, mode), None) 

73 if reason is not None: 

74 request.applymarker(pytest.mark.xfail(strict=True, reason=reason)) 

75 with tempfile.TemporaryDirectory() as out_dir: 

76 cli = KpexCLI() 

77 cli.main(['main', 

78 '--pdk', pdk, 

79 '--mode', mode, 

80 '--gds', os.path.join(TEST_DESIGNS_DIR, pdk, gds), 

81 '--out_dir', out_dir, 

82 '--2.5D', 

83 '--check', 'n']) # NOTE: the problems, rather than a failed run 

84 assert cli.rcx25_netlist_problems == [] 

85 

86 

87BLACKBOXED_DESIGNS = [ 

88 (PDK.SKY130A, 'cap_vpp_04p4x04p6_l1m1m2_noshield/cap_vpp_04p4x04p6_l1m1m2_noshield.gds.gz'), 

89 (PDK.IHP_SG13G2, 'cap_vpp_04p4x04p6_m1m2m3_substrate_pin/cap_vpp_04p4x04p6_m1m2m3_substrate_pin.gds.gz'), 

90 (PDK.IHP_SG13CMOS5L, 'cap_cmomf_w5u_l5u_m1_m4/cap_cmomf_w5u_l5u_m1_m4.gds.gz'), 

91] 

92 

93 

94@allure.parent_suite("kpex/2.5D Extraction Tests [RC netlist checks]") 

95@allure.tag("PEX", "2.5D") 

96@pytest.mark.slow 

97@pytest.mark.parametrize('pdk,gds', BLACKBOXED_DESIGNS) 

98def test_rc_netlist_with_blackboxed_devices_is_consistent(pdk: PDK, gds: str): 

99 # NOTE: the RC netlist keeps the MOM caps, which white-box mode removes 

100 with tempfile.TemporaryDirectory() as out_dir: 

101 cli = KpexCLI() 

102 cli.main(['main', 

103 '--pdk', pdk, 

104 '--mode', 'RC', 

105 '--blackbox', 'y', 

106 '--gds', os.path.join(TEST_DESIGNS_DIR, pdk, gds), 

107 '--out_dir', out_dir, 

108 '--2.5D', 

109 '--check', 'n']) # NOTE: the problems, rather than a failed run 

110 assert cli.rcx25_netlist_problems == [] 

111 

112 

113INV_1_GDS =os.path.join(TEST_DESIGNS_DIR, PDK.SKY130A, 'sky130_fd_sc_hd__inv_1', 'sky130_fd_sc_hd__inv_1.gds.gz') 

114PROBLEMS = ['port Y touches nothing'] 

115 

116 

117@allure.parent_suite("kpex/2.5D Extraction Tests [RC netlist checks]") 

118@allure.tag("PEX", "2.5D") 

119@pytest.mark.slow 

120def test_inconsistent_rc_netlist_fails_the_run_after_writing_it(): 

121 # NOTE: --check is on by default in RC mode (#215) 

122 with tempfile.TemporaryDirectory() as out_dir, \ 

123 mock.patch('klayout_pex.kpex_cli.check_rc_netlist', return_value=PROBLEMS), \ 

124 mock.patch('klayout_pex.kpex_cli.error') as error_mock, \ 

125 pytest.raises(SystemExit) as exit_info: 

126 try: 

127 KpexCLI().main(['main', '--pdk', PDK.SKY130A, '--mode', 'RC', '--gds', INV_1_GDS, 

128 '--out_dir', out_dir, '--2.5D']) 

129 finally: 

130 netlists = glob.glob(os.path.join(out_dir, '*', '*_k25d_pex_netlist.spice')) 

131 assert exit_info.value.code == ExitCode.DIAGNOSTIC_ERRORS 

132 assert [c.args[0] for c in error_mock.call_args_list] == [ 

133 "The extracted netlist is inconsistent with the LVS netlist (--check n to accept it):\n" 

134 " - port Y touches nothing" 

135 ] 

136 assert len(netlists) == 1 

137 

138 

139@allure.parent_suite("kpex/2.5D Extraction Tests [RC netlist checks]") 

140@allure.tag("PEX", "2.5D") 

141@pytest.mark.slow 

142@pytest.mark.parametrize('arguments', [['--mode', 'RC', '--check', 'n'], 

143 ['--mode', 'R']]) # NOTE: --check is off by default but in RC mode 

144def test_inconsistent_netlist_only_warns_without_check(arguments: list): 

145 with tempfile.TemporaryDirectory() as out_dir, \ 

146 mock.patch('klayout_pex.kpex_cli.check_rc_netlist', return_value=PROBLEMS), \ 

147 mock.patch('klayout_pex.kpex_cli.warning') as warning_mock: 

148 cli = KpexCLI() 

149 cli.main(['main', '--pdk', PDK.SKY130A, '--gds', INV_1_GDS, '--out_dir', out_dir, '--2.5D', *arguments]) 

150 assert cli.rcx25_netlist_problems == PROBLEMS 

151 assert "The extracted netlist is inconsistent with the LVS netlist:\n - port Y touches nothing" \ 

152 in [c.args[0] for c in warning_mock.call_args_list]