Coverage for klayout_pex/netlist/netlist_convert.py: 100%

59 statements  

« prev     ^ index     » next       coverage.py v7.17.2, created at 2026-11-06 13:14 +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# 

24 

25""" 

26The convert subcommand of the netlist tool: a SPICE netlist to the netlist format of another simulator. 

27 

28NOTE: imports neither KLayout nor generated protobuf code (but for the tech info of --pdk) 

29""" 

30 

31from __future__ import annotations 

32import argparse 

33import os 

34from typing import * 

35 

36import rich.console 

37import rich.markdown 

38import rich.text 

39from rich_argparse import RichHelpFormatter 

40 

41from ..log import error, info 

42from ..pdk_config import PDK 

43from ..pex25d.diagnostics import ExitCode 

44from ..util.argparse_helpers import add_argument_with_former_name 

45from ..version import __version__ 

46from .netlist_format import NetlistFormat 

47from .spice_reader import SpiceReader, SpiceReaderError 

48from .vacask_writer import VacaskWriter, VacaskWriterError 

49 

50 

51CONVERSION_FORMATS: Dict[NetlistFormat, str] = { 

52 NetlistFormat.VACASK: "to be included by a VACASK testbench. A subcircuit call with m = N becomes N calls, " 

53 "unless the subcircuit declares m: `--pdk` tells which of the PDK's models do " 

54 "(e.g. the MOS)", 

55} 

56"""The formats a SPICE netlist converts to, with what the help says about each""" 

57 

58 

59def _formats_epilog() -> rich.console.Group: 

60 formats = '\n'.join(f"- `{f.value}` (`{f.file_extension}`): {text}" for f, text in CONVERSION_FORMATS.items()) 

61 return rich.console.Group(rich.text.Text('Formats:', style='argparse.groups'), 

62 rich.markdown.Markdown(formats, style='argparse.text')) 

63 

64 

65def add_convert_subcommand(subparsers: argparse._SubParsersAction): 

66 parser = subparsers.add_parser( 

67 'convert', 

68 help="Convert a SPICE netlist to the netlist format of another simulator", 

69 description="Convert a SPICE netlist (e.g. an extracted one) to the netlist format of another simulator. " 

70 "Reads the elements R, C, L, K, D, M, V, I, E, F, G, H and X, " 

71 "subcircuits and .global, with numbers as values " 

72 "(no parameters, expressions, models, includes or analyses)", 

73 epilog=_formats_epilog(), 

74 formatter_class=RichHelpFormatter) 

75 parser.add_argument('input_netlist_path', 

76 help="Path to the input SPICE netlist") 

77 parser.add_argument('--to', dest='output_format', required=True, 

78 choices=[f.value for f in CONVERSION_FORMATS], 

79 help="Format of the output netlist, see the formats below") 

80 add_argument_with_former_name(parser, '--out-netlist', '--output', '-o', dest='output_path', default=None, 

81 help="Path of the output netlist (default is the input path " 

82 "with the extension of the format, see the formats below)") 

83 all_pdk_choices = [p.value for p in PDK] + list(PDK.legacy_aliases().keys()) 

84 parser.add_argument('--pdk', dest='pdk', default=None, choices=all_pdk_choices, 

85 help="PDK of the netlist: the output calls its devices as the PDK's models " 

86 "of that format declare them (default is none, see the formats below)") 

87 

88 

89def run_convert(args: argparse.Namespace) -> ExitCode: 

90 output_format = NetlistFormat(args.output_format) 

91 input_path: str = args.input_netlist_path 

92 if not os.path.isfile(input_path): 

93 error(f"Can't read the input netlist at {input_path}") 

94 return ExitCode.USAGE 

95 

96 output_path: str = args.output_path or os.path.splitext(input_path)[0] + output_format.file_extension 

97 if os.path.realpath(output_path) == os.path.realpath(input_path): 

98 error(f"The output path is the input path {input_path}, give another one with --out-netlist") 

99 return ExitCode.USAGE 

100 

101 subcircuits_with_m: Set[str] = set() 

102 if args.pdk is not None: 

103 from ..tech_info import TechInfo # NOTE: imports generated protobuf code (and KLayout) 

104 pdk = PDK.from_string(args.pdk) 

105 tech_info = TechInfo.from_json(pdk.config.tech_pb_json_path, dielectric_filter=None) 

106 subcircuits_with_m = tech_info.device_models.vacask_subcircuits_with_m 

107 

108 try: 

109 convert_to_vacask(input_path, output_path, subcircuits_with_m) 

110 except SpiceReaderError as e: 

111 error(f"Can't read the SPICE netlist {e}") 

112 return ExitCode.DIAGNOSTIC_ERRORS 

113 except VacaskWriterError as e: 

114 error(f"Can't write the SPICE netlist {input_path} as VACASK netlist: {e}") 

115 return ExitCode.DIAGNOSTIC_ERRORS 

116 

117 info(f"Wrote {output_path}") 

118 return ExitCode.OK 

119 

120 

121def convert_to_vacask(input_path: str, 

122 output_path: str, 

123 subcircuits_with_m: Collection[str]) -> str: 

124 """ 

125 Converts a SPICE netlist to a VACASK netlist, for netlist convert and kpex --netlist-format vacask 

126 

127 :param subcircuits_with_m: see VacaskWriter 

128 :return: the path written 

129 :raises SpiceReaderError: if the SPICE netlist can't be read 

130 :raises VacaskWriterError: if it has no VACASK equivalent (nothing is written then) 

131 """ 

132 netlist = SpiceReader().read(input_path) 

133 writer = VacaskWriter(subcircuits_with_m=subcircuits_with_m, 

134 header=[f"VACASK netlist converted by KLayout-PEX {__version__}", 

135 f"from the SPICE netlist {os.path.basename(input_path)}"]) 

136 writer.write(netlist, output_path) 

137 return output_path