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
« 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#
25"""
26The convert subcommand of the netlist tool: a SPICE netlist to the netlist format of another simulator.
28NOTE: imports neither KLayout nor generated protobuf code (but for the tech info of --pdk)
29"""
31from __future__ import annotations
32import argparse
33import os
34from typing import *
36import rich.console
37import rich.markdown
38import rich.text
39from rich_argparse import RichHelpFormatter
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
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"""
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'))
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)")
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
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
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
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
117 info(f"Wrote {output_path}")
118 return ExitCode.OK
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
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