aboutsummaryrefslogtreecommitdiff
path: root/pyspecs/specs.py
blob: ca0d6f9b012b906b476fb8820412fdc1a8b50d33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import subprocess
import time
from .specsparse import *

"""
Ideas:
    - generate value files
    - generate netlists based on graph representations
    - simulate
    - paralel run
    - others?

"""

default_settings = {
    "o": "traces/delete_me.vcd",
    "abstol": 1e-8,
    "reltol": 1e-4
}



def create_arguments_netlist(netlist_file, output_file, abstol, reltol):
    """ Function called by specs.simulate(), generates
    the argument string for the command-line tool.

    Returns:
        arguments (list of str):
            Formatted list of arguments required by SPECS
    """

    arguments_dictionary = { "f":netlist_file,
                             "o":output_file,
                             "abstol":abstol,
                             "reltol":reltol
                           }

    arguments = create_arguments_dictionary(arguments_dictionary)

    return arguments


def create_arguments_dictionary(arguments_dictionary:dict):
    """ Function called by specs.simulate(), generates
    the argument string for the command-line tool.

    Args:
        arguments_dictionary (dict):
            The keys of this dictionary are keywords expected
            by SPECS, while their contents hold their value.

            Example: {"f":"circuit.txt", "o":"data.vcd","
                      "abstol":1e-8, "reltol":1e-4
                     }

    Returns:
        arguments (list of str):
            Formatted list of arguments required by SPECS
    """

    arguments = []

    for keyword_string in arguments_dictionary:
        value = arguments_dictionary[keyword_string]

        # Long keywords are prepended with double dash
        if len(keyword_string) > 1:
            keyword_string = "--" + keyword_string
        else:
            keyword_string = "-" + keyword_string

        arguments.append(keyword_string)
        if value is not None:
            arguments.append(str(value))

    return arguments

def run_and_time(simulator_command, arguments, verbose):
    """Function that runs SPECS and times it.

    This is BLOCKING execution, so not intended for paralel
    usage. For this case, use par_run().

    Args:
        simulator_command (str):
            The command to call SPECS comprised of
            its directory and simulator name. Ex: ./specs
        arguments (str):
            Command-line arguments passed to SPECS
        verbose (bool):
            If true, will print SPECS output to the Python shell
    Returns:
        time_ms:
            Execution time of SPECS for that run, in miliseconds (ms)
    """

    print("Executing SPECS from python. . .")

    #spec_env = {"SC_COPYRIGHT_MESSAGE":"DISABLE"}

    process_call = simulator_command + arguments

    tic_ns = time.perf_counter_ns()
    print("Command: ", ' '.join(process_call))
    #p = subprocess.Popen(process_call, env=spec_env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    p = subprocess.Popen(process_call, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    specs_stdout, specs_stderr = p.communicate()
    p.wait()
    toc_ns = time.perf_counter_ns()

    time_ms = (toc_ns - tic_ns)*1e-6

    specs_return_code = p.returncode
    print("Execution finished with code: ", specs_return_code)

    if (not specs_return_code == 0) or (verbose):
        print("SPECS had an error. Here is the full output: ")
        for line in specs_stdout.splitlines():
            print(line.decode(encoding='utf-8', errors='ignore'))

    return time_ms, (specs_return_code, specs_stdout)

def simulate(netlist_file=None,
             custom_arguments=None,
             simulator_directory="",
             verbose=False,
             **kwargs):
    """Function that call SPECS from the Python shell

    Args:
        netlist_file (str):
            File containing the netlist description
            of the circuit to simulate. Should contain
            a simulation directive within it (.tran, .dc, .op).

            The netlist is a text file that can be:
                - Created using the KiCAD library of components in
                  its schematic editor (eeschema)
                - Created and edited by hand by knowing the syntax
                - Generated proceduraly using scripts. It's a basic .txt

            It can be achieved with custom arguments by defining
            {"f": "path/to/file"} in the dict custom_arguments.

            Defaults to "".

        output_file (str, optional):
            VCD file containing all the recorded events.
            Can be read with GTKWAVE or parsed with parse_vcd()
            Defaults to "traces/delete_me.vcd".

        abstol (double, optional):
            Field absolute tolerance for propagating events in the simulator.
            Is relevant when running circuits with feedback.

            It can be achieved with custom arguments by defining
            {"abstol": 1e-8} in the dict custom_arguments.

            Defaults to 1e-8.

        reltol (double, optional):
            Field relative tolerance for propagating events in the simulator.
            Is relevant when running circuits with feedback.

            It can be achieved with custom arguments by defining
            {"reltol": 1e-4} in the dict custom_arguments.

            Defaults to 1e-4.

        custom_arguments (dict, optional):
                         <<< ADVANCED USE >>>
            It is possible to call SPECS with many other arguments.
            You can consult them by running specs without arguments
            in the command line interface.

            If custom_arguments is defined, you need to specify
            the netlist as a part of the dictionary.

            There is no specific order that the arguments must follow.

        simulator_directory (str, optional):
            SPECS can either be included in the PATH variable
            or exist as a standalone executable. In the latter case,
            it is necessary to define its location.

            Examples:
                simulator_directory = "./"
                    Means that the executable is in the same
                    directory as this script."
                simulator_directory = "/home/user/Documents/"
                    Means that the SPECS executable is in that
                    specific path.

            Defaults to "".

        verbose (bool, optional):
            If true, will print all the outputs generated by SPECS
            to the Python shell.
    """

    assert((netlist_file is not None) or (custom_arguments is not None))


    if 'abstol' in kwargs:
        abstol = kwargs['abstol']
    else:
        abstol = default_settings['abstol']

    if 'reltol' in kwargs:
        reltol = kwargs['reltol']
    else:
        reltol = default_settings['reltol']

    if 'output_file' in kwargs:
        output_file = kwargs['output_file']
    else:
        output_file = default_settings['o']

    arguments = []
    if (netlist_file is not None):
        arguments = create_arguments_netlist(netlist_file, output_file, abstol, reltol)

    if (custom_arguments is not None):
        current_settings = default_settings.copy()
        current_settings.update(custom_arguments) # overwrites defaults with the user input where needed
        arguments += create_arguments_dictionary(current_settings)

    simulator_name = "specs"
    simulator_command = [simulator_directory + simulator_name]

    outputs = run_and_time(simulator_command, arguments, verbose)

    return outputs