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
« 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
26import glob
27import os
28import tempfile
29from unittest import mock
31import allure
32import pytest
34from klayout_pex.kpex_cli import KpexCLI
35from klayout_pex.pdk_config import PDK
36from klayout_pex.pex25d.diagnostics import ExitCode
38TEST_DESIGNS_DIR = os.path.realpath(os.path.join(__file__, '..', '..', '..', 'testdata', 'designs'))
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]
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}
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 == []
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]
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 == []
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']
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
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]