Coverage for tests/smoke/pdk_extraction_test.py: 46%

37 statements  

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

1# 

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

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

24""" 

25Smoke test: `kpex extract --pdk <PDK>` works for each bundled PDK (e.g. all rule decks are packaged). 

26 

27Requires the KLayout executable (see KPEX_KLAYOUT_EXE), see conftest.py for running the smoke tests. 

28""" 

29from __future__ import annotations 

30 

31from pathlib import Path 

32from typing import * 

33 

34import allure 

35import pytest 

36 

37from klayout_pex.pdk_config import PDK 

38 

39if TYPE_CHECKING: 

40 from .conftest import InstalledWheel # NOTE: the `installed_wheel` fixture 

41 

42 

43TESTDATA_DESIGNS_DIR = Path(__file__).resolve().parents[2] / 'testdata' / 'designs' 

44 

45# NOTE: one small layout per bundled PDK, each PDK needs one 

46SMOKE_TEST_DESIGNS: Dict[PDK, Path] = { 

47 PDK.GF180MCUD: TESTDATA_DESIGNS_DIR / 'gf180mcuD' / 'test_patterns' / 'nfet_m1.gds.gz', 

48 PDK.IHP_SG13G2: TESTDATA_DESIGNS_DIR / 'ihp-sg13g2' / 'sg13g2_a21o_1' / 'sg13g2_a21o_1.gds.gz', 

49 PDK.IHP_SG13CMOS5L: TESTDATA_DESIGNS_DIR / 'ihp-sg13cmos5l' / 'cap_cmomf_w5u_l5u_m1_m4' 

50 / 'cap_cmomf_w5u_l5u_m1_m4.gds.gz', 

51 PDK.SKY130A: TESTDATA_DESIGNS_DIR / 'sky130A' / 'inv' / 'inv.gds.gz', 

52} 

53 

54parent_suite = "Smoke Tests (installed wheel)" 

55suite = "Bundled PDKs" 

56tags = ("Smoke", "Wheel", "Packaging", "PDK") 

57 

58 

59def _tail(text: str, lines: int = 40) -> str: 

60 return '\n'.join(text.splitlines()[-lines:]) 

61 

62 

63@allure.parent_suite(parent_suite) 

64@allure.suite(suite) 

65@allure.tag(*tags) 

66@pytest.mark.parametrize('pdk', list(PDK), ids=str) 

67def test_kpex_extract(pdk: PDK, installed_wheel: InstalledWheel, tmp_path: Path): 

68 assert pdk in SMOKE_TEST_DESIGNS, f"No smoke test design for PDK {pdk}, please add one to SMOKE_TEST_DESIGNS" 

69 gds_path = SMOKE_TEST_DESIGNS[pdk] 

70 assert gds_path.is_file(), f"Smoke test design is missing: {gds_path}" 

71 

72 out_dir = tmp_path / 'out' 

73 proc = installed_wheel.run('kpex', 'extract', 

74 '--pdk', pdk, 

75 '--gds', gds_path, 

76 '--2.5D', 

77 '--out_dir', out_dir, 

78 cwd=tmp_path) 

79 allure.attach(proc.stdout + proc.stderr, name='kpex output', attachment_type=allure.attachment_type.TEXT) 

80 

81 # NOTE: output files are within <out_dir>/<gds stem>__<cell>/ 

82 lvs_logs = sorted(out_dir.glob('*/*_lvs.log')) 

83 lvsdbs = sorted(out_dir.glob('*/*.lvsdb.gz')) 

84 netlists = sorted(out_dir.glob('*/*_k25d_pex_netlist.spice')) 

85 

86 diagnostics = f"--- kpex output (tail):\n{_tail(proc.stdout + proc.stderr)}" 

87 for lvs_log in lvs_logs: 

88 lvs_log_text = lvs_log.read_text(errors='replace') 

89 allure.attach(lvs_log_text, name=lvs_log.name, attachment_type=allure.attachment_type.TEXT) 

90 errors = [line for line in lvs_log_text.splitlines() if 'ERROR' in line] 

91 diagnostics += f"\n--- ERROR lines of {lvs_log}:\n" + '\n'.join(errors[:20]) 

92 

93 returncode = proc.returncode 

94 assert returncode == 0, f"kpex failed with status {returncode}\n{diagnostics}" 

95 assert len(lvsdbs) == 1, f"Expected 1 LVSDB, got {lvsdbs}\n{diagnostics}" 

96 assert len(netlists) == 1 and netlists[0].stat().st_size > 0, \ 

97 f"Expected 1 non-empty extracted netlist, got {netlists}\n{diagnostics}"