spiceybun 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spiceybun-0.1.0/LICENSE +21 -0
- spiceybun-0.1.0/PKG-INFO +54 -0
- spiceybun-0.1.0/README.md +39 -0
- spiceybun-0.1.0/pyproject.toml +31 -0
- spiceybun-0.1.0/src/spiceybun/__init__.py +0 -0
- spiceybun-0.1.0/src/spiceybun/measure_ngspice.py +38 -0
- spiceybun-0.1.0/src/spiceybun/ngspice.py +340 -0
- spiceybun-0.1.0/src/spiceybun/tb_test.spice +19 -0
- spiceybun-0.1.0/src/spiceybun/variable.py +32 -0
spiceybun-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Filip Hormot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
spiceybun-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spiceybun
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A python verification wrapper for SPICE.
|
|
5
|
+
Author: Filip Hormot
|
|
6
|
+
Author-email: Filip Hormot <f.hormot@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: numpy>=2.4.5
|
|
10
|
+
Requires-Dist: pandas>=3.0.3
|
|
11
|
+
Requires-Python: >=3.14
|
|
12
|
+
Project-URL: Homepage, https://github.com/fhormot/spiceybun
|
|
13
|
+
Project-URL: Issues, https://github.com/fhormot/spiceybun/issues
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# SPICEybun
|
|
17
|
+
|
|
18
|
+
An NGSPICE wrapper for simplified verification.
|
|
19
|
+
|
|
20
|
+
# Usage
|
|
21
|
+
|
|
22
|
+
```Python
|
|
23
|
+
import spiceybun
|
|
24
|
+
|
|
25
|
+
simulator = spiceybun.ngspice('./netlist.spice')
|
|
26
|
+
|
|
27
|
+
simulator.add_transient(1e-6)
|
|
28
|
+
|
|
29
|
+
simulator.save_signal('V(v_out)')
|
|
30
|
+
simulator.save_signal('V(v_in)')
|
|
31
|
+
|
|
32
|
+
simulator.run()
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
# Documentation
|
|
37
|
+
TBD
|
|
38
|
+
|
|
39
|
+
# Long term roadmap
|
|
40
|
+
|
|
41
|
+
- [ ] Enabling basic functionality:
|
|
42
|
+
- [ ] All analysis statements
|
|
43
|
+
- [ ] Variables
|
|
44
|
+
- [x] Simple variables
|
|
45
|
+
- [ ] Equations
|
|
46
|
+
- [ ] Advanced variable features (distribution, limits, etc.)
|
|
47
|
+
- [ ] Measurements
|
|
48
|
+
- [x] Explicit spice input
|
|
49
|
+
- [x] Sweeps
|
|
50
|
+
- [x] Monte Carlo
|
|
51
|
+
- [ ] Advanced features
|
|
52
|
+
- [ ] Netlist from XSchem
|
|
53
|
+
- [ ] Simulator options
|
|
54
|
+
- [ ] Report generation
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# SPICEybun
|
|
2
|
+
|
|
3
|
+
An NGSPICE wrapper for simplified verification.
|
|
4
|
+
|
|
5
|
+
# Usage
|
|
6
|
+
|
|
7
|
+
```Python
|
|
8
|
+
import spiceybun
|
|
9
|
+
|
|
10
|
+
simulator = spiceybun.ngspice('./netlist.spice')
|
|
11
|
+
|
|
12
|
+
simulator.add_transient(1e-6)
|
|
13
|
+
|
|
14
|
+
simulator.save_signal('V(v_out)')
|
|
15
|
+
simulator.save_signal('V(v_in)')
|
|
16
|
+
|
|
17
|
+
simulator.run()
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
# Documentation
|
|
22
|
+
TBD
|
|
23
|
+
|
|
24
|
+
# Long term roadmap
|
|
25
|
+
|
|
26
|
+
- [ ] Enabling basic functionality:
|
|
27
|
+
- [ ] All analysis statements
|
|
28
|
+
- [ ] Variables
|
|
29
|
+
- [x] Simple variables
|
|
30
|
+
- [ ] Equations
|
|
31
|
+
- [ ] Advanced variable features (distribution, limits, etc.)
|
|
32
|
+
- [ ] Measurements
|
|
33
|
+
- [x] Explicit spice input
|
|
34
|
+
- [x] Sweeps
|
|
35
|
+
- [x] Monte Carlo
|
|
36
|
+
- [ ] Advanced features
|
|
37
|
+
- [ ] Netlist from XSchem
|
|
38
|
+
- [ ] Simulator options
|
|
39
|
+
- [ ] Report generation
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spiceybun"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Filip Hormot", email="f.hormot@gmail.com" },
|
|
6
|
+
]
|
|
7
|
+
description = "A python verification wrapper for SPICE."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.14"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"numpy>=2.4.5",
|
|
12
|
+
"pandas>=3.0.3",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICEN[CS]E*"]
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["uv_build>=0.11.14,<0.12"]
|
|
19
|
+
build-backend = "uv_build"
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=9.0.3",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
pythonpath = ["."]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/fhormot/spiceybun"
|
|
31
|
+
Issues = "https://github.com/fhormot/spiceybun/issues"
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
class Measure_ngspice():
|
|
4
|
+
def __init__(self):
|
|
5
|
+
self._measure = []
|
|
6
|
+
|
|
7
|
+
def get_all(self) -> list:
|
|
8
|
+
return self._measure
|
|
9
|
+
|
|
10
|
+
def explicit(self, measure) -> str:
|
|
11
|
+
arguments = measure.split()
|
|
12
|
+
|
|
13
|
+
self._measure.append({
|
|
14
|
+
"name": arguments[2],
|
|
15
|
+
"analysis": arguments[1],
|
|
16
|
+
"measure": measure
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return measure
|
|
20
|
+
|
|
21
|
+
def process_measure(self, path) -> object:
|
|
22
|
+
with open(path, 'r') as f:
|
|
23
|
+
header = f.readline().strip().split()
|
|
24
|
+
values = f.readline().strip().split()
|
|
25
|
+
|
|
26
|
+
# df = pd.read_csv(
|
|
27
|
+
# path,
|
|
28
|
+
# delimiter=' ',
|
|
29
|
+
# nrows=1,
|
|
30
|
+
# skipinitialspace=True
|
|
31
|
+
# )
|
|
32
|
+
|
|
33
|
+
# Extract header and first row
|
|
34
|
+
output = pd.DataFrame([values], columns=header)
|
|
35
|
+
|
|
36
|
+
output.to_csv(path, index=False, header=True)
|
|
37
|
+
|
|
38
|
+
return output
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import subprocess
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from itertools import product
|
|
9
|
+
|
|
10
|
+
from spiceybun.measure_ngspice import Measure_ngspice
|
|
11
|
+
from spiceybun.variable import Variable
|
|
12
|
+
|
|
13
|
+
class Ngspice:
|
|
14
|
+
def __init__(self, path_netlist, **kwargs):
|
|
15
|
+
self._netlist = []
|
|
16
|
+
|
|
17
|
+
self._analysis = []
|
|
18
|
+
self._variables = []
|
|
19
|
+
self._plots = []
|
|
20
|
+
|
|
21
|
+
# Control statements
|
|
22
|
+
self._plot_all = False
|
|
23
|
+
|
|
24
|
+
self._path_netlist = path_netlist
|
|
25
|
+
|
|
26
|
+
self._output_path = Path(__file__).parent
|
|
27
|
+
|
|
28
|
+
self.measure = Measure_ngspice()
|
|
29
|
+
|
|
30
|
+
# Preparation during init
|
|
31
|
+
self._read_dut_nelist()
|
|
32
|
+
|
|
33
|
+
def _read_dut_variables(self) -> None:
|
|
34
|
+
variables = []
|
|
35
|
+
|
|
36
|
+
for line in self._netlist_dut:
|
|
37
|
+
result = re.findall(r'\{\w+\}', line)
|
|
38
|
+
|
|
39
|
+
if len(result) > 0:
|
|
40
|
+
variables.extend(result)
|
|
41
|
+
|
|
42
|
+
variables = list(set(variables))
|
|
43
|
+
|
|
44
|
+
for variable in variables:
|
|
45
|
+
name = variable.strip('{}')
|
|
46
|
+
self._variables.append(Variable(name))
|
|
47
|
+
|
|
48
|
+
def get_variables(self) -> list:
|
|
49
|
+
return self._variables
|
|
50
|
+
|
|
51
|
+
def set_variable(self, name, value) -> Variable | None:
|
|
52
|
+
for variable in self._variables:
|
|
53
|
+
if variable.get_name() == name:
|
|
54
|
+
variable.set_value(value)
|
|
55
|
+
return variable
|
|
56
|
+
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
def _read_dut_nelist(self) -> None:
|
|
60
|
+
with open(self._path_netlist, 'r') as f:
|
|
61
|
+
self._netlist_dut = f.readlines()
|
|
62
|
+
|
|
63
|
+
# TODO: Strip existing control statements
|
|
64
|
+
|
|
65
|
+
# Extract variables
|
|
66
|
+
self._read_dut_variables()
|
|
67
|
+
|
|
68
|
+
def _include(self, path, **kwargs) -> str:
|
|
69
|
+
# str = f'.include "{path}"'
|
|
70
|
+
str = f'.include {path}'
|
|
71
|
+
|
|
72
|
+
if 'section' in kwargs:
|
|
73
|
+
str = str + ' ' + kwargs['section']
|
|
74
|
+
|
|
75
|
+
self._netlist.append(str)
|
|
76
|
+
|
|
77
|
+
return str
|
|
78
|
+
|
|
79
|
+
def _write_netlist_dut(self, **kwargs) -> str:
|
|
80
|
+
subfolder = kwargs.get('id', '')
|
|
81
|
+
|
|
82
|
+
output_path = os.path.join(self._output_path, subfolder)
|
|
83
|
+
output_netlist = os.path.join(output_path, 'netlist.spice')
|
|
84
|
+
|
|
85
|
+
with open(output_netlist, 'w') as f:
|
|
86
|
+
f.writelines(self._netlist_dut)
|
|
87
|
+
os.chmod(output_netlist, 0o755)
|
|
88
|
+
|
|
89
|
+
return output_netlist
|
|
90
|
+
|
|
91
|
+
def _write_netlist(self, **kwargs) -> str:
|
|
92
|
+
subfolder = kwargs.get('id', '')
|
|
93
|
+
|
|
94
|
+
self._add_control(**kwargs)
|
|
95
|
+
|
|
96
|
+
self._netlist.append('.end')
|
|
97
|
+
|
|
98
|
+
output_path = os.path.join(self._output_path, subfolder)
|
|
99
|
+
output_netlist = os.path.join(output_path, 'tb_test.spice')
|
|
100
|
+
|
|
101
|
+
with open(output_netlist, 'w') as f:
|
|
102
|
+
f.write('\n'.join(self._netlist))
|
|
103
|
+
os.chmod(output_netlist, 0o755)
|
|
104
|
+
|
|
105
|
+
self._netlist_output = output_netlist
|
|
106
|
+
|
|
107
|
+
return output_netlist
|
|
108
|
+
|
|
109
|
+
def _write_run_command(self, **kwargs) -> str:
|
|
110
|
+
subfolder = kwargs.get('id', '')
|
|
111
|
+
|
|
112
|
+
folder_path = os.path.join(self._output_path, subfolder)
|
|
113
|
+
|
|
114
|
+
path_output_netlist = self._netlist_output
|
|
115
|
+
|
|
116
|
+
command_format = "#!/bin/bash\nngspice -i -o {output_path} {input_path} -a || sh"
|
|
117
|
+
|
|
118
|
+
command_path = os.path.join(folder_path, 'run_command')
|
|
119
|
+
output_path = os.path.join(folder_path, 'output.log')
|
|
120
|
+
input_path = path_output_netlist
|
|
121
|
+
|
|
122
|
+
netlist_command = command_format.format(
|
|
123
|
+
output_path=output_path,
|
|
124
|
+
input_path=input_path
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
with open(command_path, 'w') as f:
|
|
128
|
+
f.write(netlist_command)
|
|
129
|
+
os.chmod(command_path, 0o755)
|
|
130
|
+
|
|
131
|
+
return command_path
|
|
132
|
+
|
|
133
|
+
def _add_control(self, **kwargs) -> list:
|
|
134
|
+
# Kwargs
|
|
135
|
+
mc = kwargs.get('mc', False)
|
|
136
|
+
|
|
137
|
+
control_statement = []
|
|
138
|
+
control_statement.append('\n* Control statements added by the tool')
|
|
139
|
+
|
|
140
|
+
# Parameter definitions
|
|
141
|
+
variables = kwargs.get('variables', self._variables)
|
|
142
|
+
for variable in variables:
|
|
143
|
+
control_statement.append(variable.get_value_definition())
|
|
144
|
+
|
|
145
|
+
if mc:
|
|
146
|
+
seed = kwargs.get('seed', 1)
|
|
147
|
+
mc_runs = kwargs.get('mc_runs', 350)
|
|
148
|
+
|
|
149
|
+
# Start of control statements
|
|
150
|
+
control_statement.append('\n.control')
|
|
151
|
+
|
|
152
|
+
# Section
|
|
153
|
+
# Measurement file preparation
|
|
154
|
+
control_statement.extend(self._netlist_define_measurement_setup())
|
|
155
|
+
|
|
156
|
+
# Section Monte Carlo
|
|
157
|
+
if mc:
|
|
158
|
+
control_statement.append('\n\t* Monte Carlo analysis')
|
|
159
|
+
control_statement.append(f'\tsetseed {seed}')
|
|
160
|
+
control_statement.append(f'\tlet mc_runs={mc_runs}')
|
|
161
|
+
control_statement.append('\tlet mc_index=0')
|
|
162
|
+
|
|
163
|
+
control_statement.append('\n\twhile mc_index < mc_runs')
|
|
164
|
+
|
|
165
|
+
# Section
|
|
166
|
+
# Append analysis
|
|
167
|
+
for element in self._analysis:
|
|
168
|
+
control_statement.append(f'\t\t{element}')
|
|
169
|
+
|
|
170
|
+
suffix = element.split()[0]
|
|
171
|
+
|
|
172
|
+
# Section
|
|
173
|
+
# Save statements
|
|
174
|
+
# TODO: Wrap measurements and save statments with their respective analysis
|
|
175
|
+
control_statement.extend(self._netlist_define_plot(**kwargs))
|
|
176
|
+
|
|
177
|
+
# Measureement definition and write to file
|
|
178
|
+
control_statement.extend(self._netlist_define_measurement_write())
|
|
179
|
+
|
|
180
|
+
if mc:
|
|
181
|
+
control_statement.append('\n\t\tlet mc_index = mc_index + 1')
|
|
182
|
+
control_statement.append('\t\treset')
|
|
183
|
+
|
|
184
|
+
control_statement.append('\n\t* End of Monte Carlo iteration')
|
|
185
|
+
control_statement.append('\tend')
|
|
186
|
+
|
|
187
|
+
control_statement.append('\n\texit')
|
|
188
|
+
control_statement.append('.endc\n')
|
|
189
|
+
|
|
190
|
+
self._netlist.extend(control_statement)
|
|
191
|
+
return control_statement
|
|
192
|
+
|
|
193
|
+
def _netlist_define_plot(self, **kwargs) -> list:
|
|
194
|
+
control_statement = []
|
|
195
|
+
|
|
196
|
+
subfolder = kwargs.get('id', '')
|
|
197
|
+
output_path = os.path.join(self._output_path, subfolder, 'output.raw')
|
|
198
|
+
|
|
199
|
+
# Keep vector names in the header
|
|
200
|
+
control_statement.append('\n\t\tset wr_vecnames')
|
|
201
|
+
|
|
202
|
+
# Use a single scale (column) for all signals
|
|
203
|
+
control_statement.append('\t\tset wr_singlescale')
|
|
204
|
+
|
|
205
|
+
if not self._plot_all:
|
|
206
|
+
control_statement.append(f'\t\twrdata {output_path} {' '.join(self._plots)}')
|
|
207
|
+
else:
|
|
208
|
+
control_statement.append('\t\tsave all')
|
|
209
|
+
control_statement.append(f'\t\twrdata {output_path} all')
|
|
210
|
+
|
|
211
|
+
return control_statement
|
|
212
|
+
|
|
213
|
+
def _netlist_define_measurement_setup(self) -> list:
|
|
214
|
+
control_statement = []
|
|
215
|
+
|
|
216
|
+
measurements = self.measure.get_all()
|
|
217
|
+
if len(measurements)>0:
|
|
218
|
+
control_statement.append('\t*Prepare measurement output file with a header')
|
|
219
|
+
measurement_list = ' '.join([measure['name'] for measure in measurements])
|
|
220
|
+
control_statement.append(f'\techo "{measurement_list}" > {os.path.join(self._output_path, "results", "measurements.raw\n")}')
|
|
221
|
+
|
|
222
|
+
return control_statement
|
|
223
|
+
|
|
224
|
+
def _netlist_define_measurement_write(self) -> list:
|
|
225
|
+
control_statement = []
|
|
226
|
+
|
|
227
|
+
control_statement.append('\n\t\t* Measurements')
|
|
228
|
+
|
|
229
|
+
measurements = self.measure.get_all()
|
|
230
|
+
for measure in measurements:
|
|
231
|
+
control_statement.append(f'\t\t{measure["measure"]}')
|
|
232
|
+
|
|
233
|
+
if len(measurements) > 0:
|
|
234
|
+
# control_statement.append('\n\t\tset filetype=ascii')
|
|
235
|
+
# control_statement.append('\t\tset nopadding')
|
|
236
|
+
|
|
237
|
+
# meas_string_list = ' '.join([measure['name'] for measure in measurements])
|
|
238
|
+
# control_statement.append(f'\twrdata {os.path.join(self._output_path, "measurement.raw")} {meas_string_list}')
|
|
239
|
+
|
|
240
|
+
control_statement.append('\n\t\t* Measurement output in separate files')
|
|
241
|
+
measurement_list = ' '.join([f'$&{measure['name']}' for measure in measurements])
|
|
242
|
+
control_statement.append(f'\t\techo "{measurement_list}" >> {os.path.join(self._output_path, "results", "measurements.raw")}')
|
|
243
|
+
|
|
244
|
+
return control_statement
|
|
245
|
+
|
|
246
|
+
def _run_single_run(self, **kwargs) -> str:
|
|
247
|
+
subfolder = kwargs.get('id', '')
|
|
248
|
+
|
|
249
|
+
output_path = os.path.join(self._output_path, subfolder)
|
|
250
|
+
|
|
251
|
+
# Verify the output folder exists
|
|
252
|
+
if not os.path.exists(output_path):
|
|
253
|
+
os.makedirs(output_path)
|
|
254
|
+
|
|
255
|
+
# Verify that the results folder is available
|
|
256
|
+
if not os.path.exists(os.path.join(output_path, "results")):
|
|
257
|
+
os.makedirs(os.path.join(output_path, "results"))
|
|
258
|
+
|
|
259
|
+
path_input_netlist = self._write_netlist_dut(**kwargs)
|
|
260
|
+
|
|
261
|
+
self._include(path_input_netlist)
|
|
262
|
+
path_output_netlist = self._write_netlist(**kwargs)
|
|
263
|
+
|
|
264
|
+
command_path = self._write_run_command(**kwargs)
|
|
265
|
+
|
|
266
|
+
output = subprocess.run(
|
|
267
|
+
command_path,
|
|
268
|
+
# env=self.env,
|
|
269
|
+
shell=True,
|
|
270
|
+
capture_output=True,
|
|
271
|
+
text=True
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
run_path = os.path.join(output_path, 'run.log')
|
|
275
|
+
|
|
276
|
+
with open(run_path, 'w') as f:
|
|
277
|
+
f.write(output.stdout)
|
|
278
|
+
os.chmod(command_path, 0o755)
|
|
279
|
+
|
|
280
|
+
# self.measure.process_measure(os.path.join(self._output_path, 'measurement.raw'))
|
|
281
|
+
|
|
282
|
+
return output.stdout
|
|
283
|
+
|
|
284
|
+
def _run_sweep(self, sweep_list, **kwargs) -> list:
|
|
285
|
+
results = []
|
|
286
|
+
|
|
287
|
+
for idx, run in enumerate(sweep_list):
|
|
288
|
+
self._netlist = []
|
|
289
|
+
results.append(self._run_single_run(variables=run, id=f'{idx}', **kwargs))
|
|
290
|
+
|
|
291
|
+
return results
|
|
292
|
+
|
|
293
|
+
def add_transient(self, t_stop, **kwargs) -> str:
|
|
294
|
+
# Overwrite previous transient statement if it exists
|
|
295
|
+
self._analysis = [x for x in self._analysis if not x.startswith("tran")]
|
|
296
|
+
|
|
297
|
+
# TODO: Calculate suggested/maximum steps
|
|
298
|
+
# Step 1: calculate based on stop time --> Bad for long sims with sharp transients
|
|
299
|
+
# Step 2: Find/assume fastest signal in netlist and adjust t_step accordingly
|
|
300
|
+
# --> Not accounting for fast digital signals
|
|
301
|
+
# TODO: Define transient statement using variables
|
|
302
|
+
t_step = kwargs.get('t_step', 1e-9)
|
|
303
|
+
t_start = kwargs.get('t_start', '0')
|
|
304
|
+
t_max = kwargs.get('t_max', t_step*10)
|
|
305
|
+
|
|
306
|
+
transient_statement = f'tran {t_step} {t_stop} {t_start} {t_max}'
|
|
307
|
+
|
|
308
|
+
self._analysis.append(transient_statement)
|
|
309
|
+
|
|
310
|
+
return transient_statement
|
|
311
|
+
|
|
312
|
+
def save_signal(self, signals) -> list:
|
|
313
|
+
#TODO: Check if valid net/port
|
|
314
|
+
if type(signals) is list:
|
|
315
|
+
self._plots.extend(signals)
|
|
316
|
+
else:
|
|
317
|
+
self._plots.append(signals)
|
|
318
|
+
|
|
319
|
+
return self._plots
|
|
320
|
+
|
|
321
|
+
def save_signal_all(self, flag) -> bool:
|
|
322
|
+
self._plot_all = flag
|
|
323
|
+
|
|
324
|
+
return self._plot_all
|
|
325
|
+
|
|
326
|
+
def set_output_path(self, path) -> None:
|
|
327
|
+
self._output_path = path
|
|
328
|
+
|
|
329
|
+
def run(self, **kwargs) -> str | list:
|
|
330
|
+
if len(self._variables) == 0:
|
|
331
|
+
return self._run_single_run(**kwargs)
|
|
332
|
+
|
|
333
|
+
#Create all variation combinations
|
|
334
|
+
permutation_pre_list = [variable.get_split() for variable in self._variables]
|
|
335
|
+
permutations = list(product(*permutation_pre_list))
|
|
336
|
+
|
|
337
|
+
if len(permutations) == 1:
|
|
338
|
+
return self._run_single_run(**kwargs)
|
|
339
|
+
|
|
340
|
+
return self._run_sweep(permutations, **kwargs)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
* Control statements added by the tool
|
|
3
|
+
.param t_edge=
|
|
4
|
+
.param c_load=
|
|
5
|
+
.param v_vdd=
|
|
6
|
+
|
|
7
|
+
.control
|
|
8
|
+
tran 1e-11 500e-9 0 100e-12
|
|
9
|
+
|
|
10
|
+
set wr_vecnames
|
|
11
|
+
set wr_singlescale
|
|
12
|
+
wrdata /home/fhormot/wk/xschem/spipy/src/spipy/output.raw
|
|
13
|
+
|
|
14
|
+
* Measurements
|
|
15
|
+
|
|
16
|
+
exit
|
|
17
|
+
.endc
|
|
18
|
+
|
|
19
|
+
.end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
class Variable:
|
|
4
|
+
def __init__(self, name: str):
|
|
5
|
+
self._name = name
|
|
6
|
+
self._value = ''
|
|
7
|
+
|
|
8
|
+
def get_name(self) -> str:
|
|
9
|
+
return self._name
|
|
10
|
+
|
|
11
|
+
def get_value(self) -> str:
|
|
12
|
+
return self._value
|
|
13
|
+
|
|
14
|
+
def get_value_definition(self) -> str:
|
|
15
|
+
return f'.param {self._name}={self._value}'
|
|
16
|
+
|
|
17
|
+
def set_value(self, value: str) -> None:
|
|
18
|
+
self._value = value
|
|
19
|
+
|
|
20
|
+
def get_split(self) -> list:
|
|
21
|
+
if not isinstance(self._value, list):
|
|
22
|
+
return [self]
|
|
23
|
+
|
|
24
|
+
return_list = []
|
|
25
|
+
|
|
26
|
+
for idx, variable in enumerate(self.get_value()):
|
|
27
|
+
object_copy = copy.deepcopy(self)
|
|
28
|
+
|
|
29
|
+
object_copy.set_value(variable)
|
|
30
|
+
return_list.append(object_copy)
|
|
31
|
+
|
|
32
|
+
return return_list
|