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

58 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-19 18:50 +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 

60 

61def prepare_magic_script(gds_path: str, 

62 cell_name: str, 

63 run_dir_path: str, 

64 script_path: str, 

65 output_netlist_path: str, 

66 pex_mode: MagicPEXMode, 

67 c_threshold: float, 

68 r_threshold: float, 

69 tolerance: float, 

70 halo: Optional[float], 

71 short_mode: MagicShortMode, 

72 merge_mode: MagicMergeMode): 

73 gds_path = os.path.abspath(gds_path) 

74 run_dir_path = os.path.abspath(run_dir_path) 

75 output_netlist_path = os.path.abspath(output_netlist_path) 

76 

77 halo_scale = 200.0 

78 halo_decl = '' if halo is None else f"\nextract halo {round(halo * halo_scale)}" 

79 

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

81 # extract do resistance 

82 # ext2spice rthresh {r_threshold} 

83 # 

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

85 

86 script: str = "" 

87 match pex_mode: 

88 case MagicPEXMode.CC: 

89 script = f"""# Generated by kpex {__version__} 

90crashbackups stop 

91drc off 

92gds read {gds_path} 

93load {cell_name} 

94select top cell 

95flatten {cell_name}_flat 

96load {cell_name}_flat 

97cellname delete {cell_name} -noprompt 

98cellname rename {cell_name}_flat {cell_name} 

99select top cell 

100extract path {run_dir_path}{halo_decl} 

101extract all 

102ext2spice short {short_mode} 

103ext2spice merge {merge_mode} 

104ext2spice cthresh {c_threshold} 

105ext2spice subcircuits top on 

106ext2spice format ngspice 

107ext2spice -p {run_dir_path} -o {output_netlist_path} 

108quit -noprompt""" 

109 case MagicPEXMode.RC: 

110 script = f"""# Generated by kpex {__version__} 

111crashbackups stop 

112drc off 

113gds read {gds_path} 

114load {cell_name} 

115select top cell 

116flatten {cell_name}_flat 

117load {cell_name}_flat 

118cellname delete {cell_name} -noprompt 

119cellname rename {cell_name}_flat {cell_name} 

120select top cell 

121extract path {run_dir_path}{halo_decl} 

122extract all 

123ext2sim labels on 

124ext2sim -p {run_dir_path} 

125extresist tolerance {tolerance} 

126extresist all 

127ext2spice short {short_mode} 

128ext2spice merge {merge_mode} 

129ext2spice cthresh {c_threshold} 

130ext2spice extresist on 

131ext2spice subcircuits top on 

132ext2spice format ngspice 

133ext2spice -p {run_dir_path} -o {output_netlist_path} 

134quit -noprompt 

135""" 

136 case MagicPEXMode.R: 

137 script = f"""# Generated by kpex {__version__} 

138crashbackups stop 

139drc off 

140gds read {gds_path} 

141load {cell_name} 

142select top cell 

143flatten {cell_name}_flat 

144load {cell_name}_flat 

145cellname delete {cell_name} -noprompt 

146cellname rename {cell_name}_flat {cell_name} 

147select top cell 

148extract path {run_dir_path}{halo_decl} 

149extract no capacitance 

150extract no coupling 

151extract all 

152ext2sim labels on 

153ext2sim -p {run_dir_path} 

154extresist tolerance {tolerance} 

155extresist all 

156ext2spice short {short_mode} 

157ext2spice merge {merge_mode} 

158ext2spice extresist on 

159ext2spice subcircuits top on 

160ext2spice format ngspice 

161ext2spice -p {run_dir_path} -o {output_netlist_path} 

162quit -noprompt 

163""" 

164 

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

166 f.write(script) 

167 

168def run_magic(exe_path: str, 

169 magicrc_path: str, 

170 script_path: str, 

171 log_path: str): 

172 args = [ 

173 exe_path, 

174 '-dnull', # 

175 '-noconsole', # 

176 '-rcfile', # 

177 magicrc_path, # 

178 script_path, # TCL script 

179 ] 

180 

181 info('Calling MAGIC') 

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

183 

184 rule('MAGIC Output') 

185 

186 start = time.time() 

187 

188 proc = subprocess.Popen(args, 

189 stdin=subprocess.DEVNULL, 

190 stdout=subprocess.PIPE, 

191 stderr=subprocess.STDOUT, 

192 universal_newlines=True, 

193 text=True) 

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

195 while True: 

196 line = proc.stdout.readline() 

197 if not line: 

198 break 

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

200 f.writelines([line]) 

201 proc.wait() 

202 

203 duration = time.time() - start 

204 

205 rule() 

206 

207 if proc.returncode == 0: 

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

209 else: 

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

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

212