Coverage for klayout_pex/klayout/lvs_runner.py: 100%
49 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-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#
24from __future__ import annotations
26import os
27import subprocess
28import time
29from typing import *
31from ..log import (
32 debug,
33 info,
34 warning,
35 error,
36 subproc,
37 rule
38)
41class LVSError(Exception):
42 """
43 KLayout LVS did not produce the LVS database that extraction depends on.
44 """
45 pass
48class LVSRunner:
49 # Parameters passed to the LVS scripts (-rd name=value), besides the paths and the verbosity
50 SCRIPT_PARAMETERS: Dict[str, str] = {
51 'thr': '22',
52 'run_mode': 'deep',
53 'spice_net_names': 'true',
54 'spice_comments': 'false',
55 'scale': 'false',
56 'schematic_simplify': 'false',
57 'net_only': 'false',
58 'top_lvl_pins': 'true',
59 'combine': 'false',
60 'combine_devices': 'false', # IHP
61 'purge': 'false',
62 'purge_nets': 'false',
63 'no_simplify': 'true', # IHP
64 }
66 # Script parameters that can't change the LVS database:
67 # the thread count, and the options of the SPICE writer for the extracted netlist
68 #
69 # NOTE: any other parameter is assumed to change it, so a new one is part of
70 # the LVS cache fingerprint unless it is listed here
71 NEUTRAL_SCRIPT_PARAMETERS: FrozenSet[str] = frozenset({
72 'thr',
73 'spice_net_names',
74 'spice_comments',
75 })
77 @classmethod
78 def result_script_parameters(cls) -> Dict[str, str]:
79 """
80 The script parameters that can change the LVS database (see LVSInputFingerprint)
81 """
82 return {name: value
83 for name, value in cls.SCRIPT_PARAMETERS.items()
84 if name not in cls.NEUTRAL_SCRIPT_PARAMETERS}
86 @staticmethod
87 def run_klayout_lvs(exe_path: str,
88 lvs_script: str,
89 gds_path: str,
90 schematic_path: str,
91 log_path: str,
92 lvsdb_path: str,
93 netlist_path: str,
94 verbose: bool):
95 # NOTE: target_netlist must always be passed, otherwise the LVS scripts fall back to
96 # a path relative to the (in batch mode empty) active cell view,
97 # i.e. next to the input layout or into the parent of the working directory
98 args = [
99 exe_path,
100 '-b',
101 '-r', lvs_script,
102 '-rd', f"input={os.path.abspath(gds_path)}",
103 '-rd', f"report={os.path.abspath(lvsdb_path)}",
104 '-rd', f"target_netlist={os.path.abspath(netlist_path)}",
105 '-rd', f"schematic={os.path.abspath(schematic_path)}",
106 ]
107 for name, value in LVSRunner.SCRIPT_PARAMETERS.items():
108 args += ['-rd', f"{name}={value}"]
109 args += ['-rd', f"verbose={'true' if verbose else 'false'}"]
110 rule('Calling KLayout LVS script')
111 subproc(' '.join(args))
112 subproc(log_path)
114 # A report left over from an earlier run in the same output directory
115 # must not be mistaken for the result of this one
116 if os.path.exists(lvsdb_path):
117 os.remove(lvsdb_path)
119 klayout_errors = []
120 start = time.time()
122 proc = subprocess.Popen(args,
123 stdin=subprocess.DEVNULL,
124 stdout=subprocess.PIPE,
125 stderr=subprocess.STDOUT,
126 universal_newlines=True,
127 text=True)
128 with open(log_path, 'w', encoding='utf-8') as f:
129 while True:
130 line = proc.stdout.readline()
131 if not line:
132 break
133 subproc(line[:-1]) # remove newline
134 f.writelines([line])
135 if line.startswith('ERROR:'):
136 klayout_errors.append(line.rstrip())
137 proc.wait()
139 duration = time.time() - start
141 rule()
143 # NOTE: the report is the criterion, not the status code:
144 # - a script error (e.g. an unresolvable %include) aborts before the report is written
145 # - a netlist mismatch against the schematic is no error for PEX, the report is
146 # still written, but some LVS scripts (e.g. sky130) exit with 1 in that case
147 if not os.path.isfile(lvsdb_path):
148 msg = f"KLayout LVS exited with status code {proc.returncode} after {'%.4g' % duration}s " \
149 f"without writing the LVS database {lvsdb_path}"
150 if klayout_errors:
151 msg += "\nKLayout reported:\n" + '\n'.join(f" {e}" for e in klayout_errors)
152 msg += f"\nSee LVS log file for details: {log_path}"
153 raise LVSError(msg)
155 if proc.returncode == 0:
156 info(f"klayout LVS succeeded after {'%.4g' % duration}s")
157 else:
158 info(f"klayout LVS finished with status code {proc.returncode} after {'%.4g' % duration}s, "
159 f"most likely due to a netlist mismatch against the schematic (irrelevant for PEX), "
160 f"see log file: {log_path}")