Coverage for klayout_pex/klayout/lvs_runner.py: 100%

54 statements  

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

28import subprocess 

29import time 

30from typing import * 

31 

32from ..log import ( 

33 debug, 

34 info, 

35 warning, 

36 error, 

37 subproc, 

38 rule 

39) 

40 

41 

42class LVSError(Exception): 

43 """ 

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

45 """ 

46 pass 

47 

48 

49class LVSRunner: 

50 # Parameters passed to the LVS scripts (-rd name=value), besides the paths and the verbosity 

51 SCRIPT_PARAMETERS: Dict[str, str] = { 

52 'thr': '22', 

53 'run_mode': 'deep', 

54 'spice_net_names': 'true', 

55 'spice_comments': 'false', 

56 'scale': 'false', 

57 'schematic_simplify': 'false', 

58 'net_only': 'false', 

59 'top_lvl_pins': 'true', 

60 'combine': 'false', 

61 'combine_devices': 'false', # IHP 

62 'purge': 'false', 

63 'purge_nets': 'false', 

64 'no_simplify': 'true', # IHP 

65 } 

66 

67 # Script parameters that can't change the LVS database: 

68 # the thread count, and the options of the SPICE writer for the extracted netlist 

69 # 

70 # NOTE: any other parameter is assumed to change it, so a new one is part of 

71 # the LVS cache fingerprint unless it is listed here 

72 NEUTRAL_SCRIPT_PARAMETERS: FrozenSet[str] = frozenset({ 

73 'thr', 

74 'spice_net_names', 

75 'spice_comments', 

76 }) 

77 

78 @classmethod 

79 def result_script_parameters(cls) -> Dict[str, str]: 

80 """ 

81 The script parameters that can change the LVS database (see LVSInputFingerprint) 

82 """ 

83 return {name: value 

84 for name, value in cls.SCRIPT_PARAMETERS.items() 

85 if name not in cls.NEUTRAL_SCRIPT_PARAMETERS} 

86 

87 @staticmethod 

88 def write_dummy_schematic(path: str, cell_name: str): 

89 """ 

90 The schematic of the PEX-LVS, empty: it compares the layout with a dummy schematic only, 

91 as KLayout writes the LVS database after a comparison: the alignment flattens every circuit 

92 of the layout into its top circuit, whose devices and nets the extraction takes, while a schematic 

93 with subcircuits would keep their circuits (#330) 

94 """ 

95 # NOTE: the same bytes on every platform (no CRLF on Windows), 

96 # as the fingerprints of the LVS inputs hash it (LVS cache, testdata/lvsdb) 

97 with open(path, 'w', encoding='utf-8', newline='\n') as f: 

98 f.writelines([ 

99 f".subckt {cell_name} VDD VSS\n", 

100 '.ends\n', 

101 '.end\n' 

102 ]) 

103 

104 @staticmethod 

105 def run_klayout_lvs(exe_path: str, 

106 lvs_script: str, 

107 gds_path: str, 

108 schematic_path: str, 

109 log_path: str, 

110 lvsdb_path: str, 

111 netlist_path: str, 

112 verbose: bool): 

113 # NOTE: target_netlist must always be passed, otherwise the LVS scripts fall back to 

114 # a path relative to the (in batch mode empty) active cell view, 

115 # i.e. next to the input layout or into the parent of the working directory 

116 args = [ 

117 exe_path, 

118 '-b', 

119 '-r', lvs_script, 

120 '-rd', f"input={os.path.abspath(gds_path)}", 

121 '-rd', f"report={os.path.abspath(lvsdb_path)}", 

122 '-rd', f"target_netlist={os.path.abspath(netlist_path)}", 

123 '-rd', f"schematic={os.path.abspath(schematic_path)}", 

124 ] 

125 for name, value in LVSRunner.SCRIPT_PARAMETERS.items(): 

126 args += ['-rd', f"{name}={value}"] 

127 args += ['-rd', f"verbose={'true' if verbose else 'false'}"] 

128 rule('Calling KLayout LVS script') 

129 subproc(' '.join(args)) 

130 subproc(log_path) 

131 

132 # A report left over from an earlier run in the same output directory 

133 # must not be mistaken for the result of this one 

134 if os.path.exists(lvsdb_path): 

135 os.remove(lvsdb_path) 

136 

137 klayout_errors = [] 

138 start = time.time() 

139 

140 proc = subprocess.Popen(args, 

141 stdin=subprocess.DEVNULL, 

142 stdout=subprocess.PIPE, 

143 stderr=subprocess.STDOUT, 

144 universal_newlines=True, 

145 text=True, 

146 encoding='utf-8', 

147 errors='replace') 

148 with open(log_path, 'w', encoding='utf-8') as f: 

149 while True: 

150 line = proc.stdout.readline() 

151 if not line: 

152 break 

153 subproc(line[:-1]) # remove newline 

154 f.writelines([line]) 

155 # NOTE: the log entries behind an error of the LVS have its reason, e.g. "[device-extract] In cell ..." 

156 if line.startswith('ERROR:') or re.match(r'\[[\w-]+] ', line): 

157 klayout_errors.append(line.rstrip()) 

158 proc.wait() 

159 

160 duration = time.time() - start 

161 

162 rule() 

163 

164 # NOTE: the report is the criterion, not the status code: 

165 # a script error (e.g. an unresolvable %include) aborts before the report is written 

166 if not os.path.isfile(lvsdb_path): 

167 msg = f"KLayout LVS exited with status code {proc.returncode} after {'%.4g' % duration}s " \ 

168 f"without writing the LVS database {lvsdb_path}" 

169 if klayout_errors: 

170 msg += "\nKLayout reported:\n" + '\n'.join(f" {e}" for e in klayout_errors) 

171 msg += f"\nSee LVS log file for details: {log_path}" 

172 raise LVSError(msg) 

173 

174 if proc.returncode == 0: 

175 info(f"klayout LVS succeeded after {'%.4g' % duration}s") 

176 else: 

177 info(f"klayout LVS finished with status code {proc.returncode} after {'%.4g' % duration}s, " 

178 f"see log file: {log_path}")