Coverage for klayout_pex/magic/magic_runner.py: 43%

72 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 15:48 +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 enum import StrEnum 

25import time 

26from typing import * 

27 

28import os 

29import subprocess 

30 

31from ..log import ( 

32 info, 

33 # warning, 

34 rule, 

35 subproc, 

36) 

37from ..version import __version__ 

38 

39 

40class MagicPEXMode(StrEnum): 

41 CC = "CC" 

42 RC = "RC" 

43 R = "R" 

44 DEFAULT = CC 

45 

46 

47class MagicShortMode(StrEnum): 

48 NONE = "none" 

49 RESISTOR = "resistor" 

50 VOLTAGE = "voltage" 

51 DEFAULT = NONE 

52 

53 

54class MagicMergeMode(StrEnum): 

55 NONE = "none" # don't merge parallel devices 

56 CONSERVATIVE = "conservative" # merge devices with same L, W 

57 AGGRESSIVE = "aggressive" # merge devices with same L 

58 DEFAULT = NONE 

59 

60class MagicUniqueMode(StrEnum): 

61 IMPLICIT = "implicit" # use Magic's default value 

62 OFF = "off" # Force off 

63 ON = "on" # Force on all nets 

64 NO_TOP_PORTS = "no_top_ports" # Force all nets, except top-level ports 

65 DEFAULT = IMPLICIT 

66 

67 def to_cmd(self) -> str: 

68 """Translates enum to Magic command""" 

69 match self: 

70 case MagicUniqueMode.OFF: 

71 return "extract no unique" 

72 case MagicUniqueMode.ON: 

73 return "extract do unique" 

74 case MagicUniqueMode.NO_TOP_PORTS: 

75 return "extract do unique notopports" 

76 case MagicUniqueMode.IMPLICIT | _: 

77 return "" 

78 

79def prepare_magic_script(gds_path: str, 

80 cell_name: str, 

81 run_dir_path: str, 

82 script_path: str, 

83 output_netlist_path: str, 

84 pex_mode: MagicPEXMode, 

85 c_threshold: float, 

86 r_threshold: float, 

87 threshold: float, 

88 min_res: float, 

89 min_delay: float, 

90 halo: Optional[float], 

91 short_mode: MagicShortMode, 

92 merge_mode: MagicMergeMode, 

93 unique_mode: MagicUniqueMode): 

94 gds_path = os.path.abspath(gds_path) 

95 run_dir_path = os.path.abspath(run_dir_path) 

96 output_netlist_path = os.path.abspath(output_netlist_path) 

97 

98 halo_scale = 200.0 

99 

100 # NOTE: do not that those are doing nothing useful: 

101 # extract do resistance 

102 # ext2spice rthresh {r_threshold} 

103 # 

104 # see https://github.com/martinjankoehler/magic/issues/4#issuecomment-3381935719 

105 

106 has_res = pex_mode in (MagicPEXMode.RC, MagicPEXMode.R) 

107 has_cap = pex_mode in (MagicPEXMode.RC, MagicPEXMode.CC) 

108 # Per corner extraction modes 

109 ext_coupling: str = "do" if has_cap else "no" 

110 ext_cap: str = "do" if has_cap else "no" 

111 ext_res: str = "do" if has_res else "no" 

112 

113 script_lines: list[str|None] = [ 

114 f"# Generated by kpex {__version__}", 

115 "crashbackups stop", 

116 "drc off", 

117 f"gds read {gds_path}", 

118 f"load {cell_name}", 

119 "select top cell", 

120 f"flatten {cell_name}_flat", 

121 f"load {cell_name}_flat", 

122 f"cellname delete {cell_name} -noprompt", 

123 f"cellname rename {cell_name}_flat {cell_name}", 

124 "select top cell", 

125 f"extresist threshold {threshold}" if threshold is not None else None, 

126 f"extresist minres {min_res}" if min_res is not None else None, 

127 f"extresist mindelay {min_delay}" if min_delay is not None else None, 

128 f"extract path {run_dir_path}", 

129 f"extract halo {round(halo * halo_scale)}" if halo is not None else None, 

130 f"extract {ext_cap} capacitance", 

131 f"extract {ext_coupling} coupling", 

132 f"extract {ext_res} resistance", 

133 f"{unique_mode.to_cmd()}", 

134 "extract all", 

135 f"ext2spice short {short_mode}", 

136 f"ext2spice merge {merge_mode}", 

137 f"ext2spice cthresh {c_threshold}" if has_cap else None, 

138 "ext2spice extresist on" if has_res else None, 

139 "ext2spice subcircuits top on", 

140 "ext2spice format ngspice", 

141 f"ext2spice -p {run_dir_path} -o {output_netlist_path}", 

142 "quit -noprompt" 

143 ] 

144 

145 # filter out all empty strings and write out script 

146 script: str = "\n".join(filter(None, script_lines)) + "\n" 

147 

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

149 f.write(script) 

150 

151def run_magic(exe_path: str, 

152 magicrc_path: str, 

153 script_path: str, 

154 log_path: str): 

155 args = [ 

156 exe_path, 

157 '-dnull', # 

158 '-noconsole', # 

159 '-rcfile', # 

160 magicrc_path, # 

161 script_path, # TCL script 

162 ] 

163 

164 info('Calling MAGIC') 

165 subproc(f"{' '.join(args)}, output file: {log_path}") 

166 

167 rule('MAGIC Output') 

168 

169 start = time.time() 

170 

171 proc = subprocess.Popen(args, 

172 stdin=subprocess.DEVNULL, 

173 stdout=subprocess.PIPE, 

174 stderr=subprocess.STDOUT, 

175 universal_newlines=True, 

176 text=True) 

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

178 while True: 

179 line = proc.stdout.readline() 

180 if not line: 

181 break 

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

183 f.writelines([line]) 

184 proc.wait() 

185 

186 duration = time.time() - start 

187 

188 rule() 

189 

190 if proc.returncode == 0: 

191 info(f"MAGIC succeeded after {'%.4g' % duration}s") 

192 else: 

193 raise Exception(f"MAGIC failed with status code {proc.returncode} after {'%.4g' % duration}s, " 

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

195