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
« 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 *
28import os
29import subprocess
31from ..log import (
32 info,
33 # warning,
34 rule,
35 subproc,
36)
37from ..version import __version__
40class MagicPEXMode(StrEnum):
41 CC = "CC"
42 RC = "RC"
43 R = "R"
44 DEFAULT = CC
47class MagicShortMode(StrEnum):
48 NONE = "none"
49 RESISTOR = "resistor"
50 VOLTAGE = "voltage"
51 DEFAULT = NONE
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
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)
77 halo_scale = 200.0
78 halo_decl = '' if halo is None else f"\nextract halo {round(halo * halo_scale)}"
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
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"""
165 with open(script_path, 'w', encoding='utf-8') as f:
166 f.write(script)
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 ]
181 info('Calling MAGIC')
182 subproc(f"{' '.join(args)}, output file: {log_path}")
184 rule('MAGIC Output')
186 start = time.time()
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()
203 duration = time.time() - start
205 rule()
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}")