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

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 

25 

26import os 

27import subprocess 

28import time 

29from typing import * 

30 

31from ..log import ( 

32 debug, 

33 info, 

34 warning, 

35 error, 

36 subproc, 

37 rule 

38) 

39 

40 

41class LVSError(Exception): 

42 """ 

43 KLayout LVS did not produce the LVS database that extraction depends on. 

44 """ 

45 pass 

46 

47 

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 } 

65 

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 }) 

76 

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} 

85 

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) 

113 

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) 

118 

119 klayout_errors = [] 

120 start = time.time() 

121 

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() 

138 

139 duration = time.time() - start 

140 

141 rule() 

142 

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) 

154 

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}")