"""
=====
Spice
=====
Analog simulation interface package for TheSyDeKick.
Provides utilities to import spice-like modules to Python environment and
generate testbenches for the various simulation cases.
Initially written by Okko Järvinen, 2019
Notes for developers
--------------------
There are now two ways to provide simulator dependent structures that are
(most of the time) followed:
1. Simulator dependent *properties* are defined in packages
<simulator>/<simulator>.py that are used as `spice_simulator` instance
in this spice class inside *spice_simulator* property. Properties and
attributed of instance of *this class* (i.e. all TheSyDeKick Entitites)
are made visible to spice_simulators through passing the *self* as *parent*
argument in instance creation. Properties defined inside
*spice_simulator* are accessed and set through corresponding properties of
this class.
2. This is an interface package, generic spice simulation
related methods should be provided in *spice_methods* module.
"""
import os
import sys
import subprocess
import shlex
import pdb
import shutil
import time
import traceback
import threading
import timeit
from datetime import datetime
from abc import *
from thesdk import *
import numpy as np
from numpy import genfromtxt
import pandas as pd
from functools import reduce
from spice.spice_common import *
from spice.testbench import testbench as stb
from spice.spice_simcmd import spice_simcmd as spice_simcmd
from spice.spice_iofile import spice_iofile as spice_iofile
from spice.spice_dcsource import spice_dcsource as spice_dcsource
from spice.spice_module import spice_module as spice_module
# Simulator modules
from spice.ngspice.ngspice import ngspice
from spice.eldo.eldo import eldo
from spice.spectre.spectre import spectre
[docs]
class spice(spice_common):
@property
def si_prefix_mult(self):
"""dict : Dictionary mapping SI-prefixes to multipliers.
"""
if hasattr(self, '_si_prefix_mult'):
return self._si_prefix_mult
else:
self._si_prefix_mult = {
'E':1e18,
'P':1e15,
'T':1e12,
'G':1e9,
'M':1e6,
'k':1e3,
'm':1e-3,
'u':1e-6,
'n':1e-9,
'p':1e-12,
'f':1e-15,
'a':1e-18,
}
return self._si_prefix_mult
@property
def spice_simulator(self):
"""The simulator specific operation is defined with an instance of
simulator specific class. Properties and methods return values from that class.
:type: ngspice
:type: eldo
:type: spectre
"""
if not hasattr(self,'_spice_simulator'):
if self.model == 'ngspice':
self._spice_simulator=ngspice(parent=self)
if self.model == 'eldo':
self._spice_simulator=eldo(parent=self)
if self.model == 'spectre':
self._spice_simulator=spectre(parent=self)
return self._spice_simulator
@property
def syntaxdict(self):
"""dict : Internally used dictionary for common syntax conversions between
Spectre, Eldo, and Ngspice.
"""
if not hasattr(self,'_syntaxdict'):
self._syntaxdict = self.spice_simulator.syntaxdict
return self._syntaxdict
@syntaxdict.setter
def syntaxdict(self,value):
self._spice_simulator.syntaxdict=value
@property
def preserve_spicefiles(self):
"""bool : If True, do not delete generated Spice files (testbench, subcircuit,
etc.) after simulations. Useful for debugging.
(Default : False )
"""
if not hasattr(self,'_preserve_spicefiles'):
self._preserve_spicefiles=False
return self._preserve_spicefiles
@preserve_spicefiles.setter
def preserve_spicefiles(self,value):
self._preserve_spicefiles=value
@property
def spicesimpath(self):
""" str : Path to the directory where the simulation results are stored.
( Default : self.simpath )
"""
if not hasattr(self,'_spicesimpath'):
self._spicesimpath=self.simpath
return self._spicesimpath
[docs]
def delete_spicesimpath(self):
""" Method to clean up files from spicesimpath.
"""
if os.path.exists(self.spicesimpath):
# This is used to check if the waveform database would prevent the deletion of the directory
keepdb = False
# Collect iofile filepaths not to delete them
iofilepaths = []
for name,val in self.iofile_bundle.Members.items():
for fpath in val.file:
iofilepaths.append(fpath)
# Delete everything (conditionally skip iofiles or spicefiles)
for target in os.listdir(self.spicesimpath):
targetpath = '%s/%s' % (self.spicesimpath,target)
try:
if targetpath not in iofilepaths:
# Target is a spicefile (anything that isn't an iofile)
if self.preserve_spicefiles:
self.print_log(type='I',msg='Preserving %s' % targetpath)
else:
if targetpath == self.spicedbpath and self.interactive_spice:
keepdb = True
self.print_log(type='I', msg='Preserving %s due to interactive_spice' % targetpath)
continue
if os.path.isdir(targetpath):
shutil.rmtree(targetpath)
else:
os.remove(targetpath)
self.print_log(type='D',msg='Removing %s' % targetpath)
except:
self.print_log(type='W',msg='Could not remove %s' % targetpath)
if not keepdb and not self.preserve_iofiles and not self.preserve_spicefiles:
try:
# Eldo needs some time to disconnect from the jwdb server
# Another dirty hack to check that the process is dead before cleaning
# TODO: figure out if this can be prevented
# This is exceptionally here as eldo is the only deviation from the rule
# strictly it should be eldo's pecific module.
if self.model == 'eldo':
self.print_log(type='I',msg='Waiting for Eldo to exit...')
waittime = 0
while os.system('pgrep \"easynch_64.exe\" >/dev/null') == 0:
time.sleep(1)
waittime += 1
if waittime > 60:
break
shutil.rmtree(self.spicesimpath)
self.print_log(type='D',msg='Removing %s/' % self.spicesimpath)
except:
self.print_log(type='W',msg='Could not remove %s/' % self.spicesimpath)
@property
def distributed_run(self):
"""bool : If True, distributes applicable simulations (currently DC sweep
supported) into the LSF cluster. The number of subprocesses launched is
set by self.num_processes.
(Default False)
"""
if hasattr(self, '_distributed_run'):
return self._distributed_run
else:
self._distributed_run=False
return self.distributed_run
@distributed_run.setter
def distributed_run(self, value):
self._distributed_run=value
@property
def num_processes(self):
"""int : Maximum number of spawned child processes for distributed runs.
"""
if hasattr(self, '_num_processes'):
return self._num_processes
else:
self._num_processes=10
return self.num_processes
@num_processes.setter
def num_processes(self, value):
self._num_processes=int(value)
@property
def load_state(self):
"""str : Feature for loading results of previous simulation. The Spice
simulation is not re-executed, but the outputs will be read from
existing files. The string value should be the `runname` of the desired
simulation.
( Default '' )
Example
-------
Loading the most recent result automatically::
self.load_state = 'last'
# or
self.load_state = 'latest'
Loading a specific past result using the `runname`::
self.load_state = '20201002103638_tmpdbw11nr4'
List available results by providing any non-existent `runname`::
self.load_state = 'this_does_not_exist'
"""
if not hasattr(self,'_load_state'):
self._load_state=''
return self._load_state
@load_state.setter
def load_state(self,value):
self._load_state=value
@property
def spicecorner(self):
"""dict : Feature for specifying the 'section' of the model library file and
simulation temperature. The path to model libraries should be set in
TheSDK.config as either ELDOLIBFILE, SPECTRELIBFILE or NGSPICELIBFILE
variable.
Example
-------
::
self.spicecorner = {
'temp': 27,
'corner': 'top_tt'
}
"""
if hasattr(self,'_spicecorner'):
return self._spicecorner
else:
self._spicecorner= {
'temp': 27,
'corner': ''
}
return self._spicecorner
@spicecorner.setter
def spicecorner(self,value):
self._spicecorner=value
@property
def spiceoptions(self):
"""dict : Feature for specifying options for spice simulation. The key is the
name of the option (as in simulator manual specifies), and the value is
the value given to said option. Valid key-value pairs can be found from
the manual of the simulator (Eldo, Spectre or Ngspice).
Example
-------
::
self.spiceoptions = {
'save': 'lvlpub',
'nestlvl': '1',
'pwr': 'subckts',
'digits': '12'
}
"""
if hasattr(self,'_spiceoptions'):
return self._spiceoptions
else:
self._spiceoptions={}
return self._spiceoptions
@spiceoptions.setter
def spiceoptions(self,value):
self._spiceoptions=value
@property
def spiceparameters(self):
"""dict : Feature for specifying simulation parameters for spice simulation. The
key is the name of the parameter , and the value is the value given to
said parameter.
Example
-------
::
self.spiceparameters = {
'nf_pmos': 8,
'nf_nmos': 4,
'ibias': 100e-6
}
"""
if not hasattr(self, '_spiceparameters'):
self._spiceparameters =dict([])
return self._spiceparameters
@spiceparameters.setter
def spiceparameters(self,value):
self._spiceparameters = value
@property
def interactive_spice(self):
"""bool : Launch simulator in interactive mode. A waveform viewer (ezwave by
default) is opened during the simulation for debugging. See
`plotprogram` for selecting waveform viewer program.
( Default : False )
"""
if hasattr(self,'_interactive_spice'):
return self._interactive_spice
else:
self._interactive_spice=False
return self._interactive_spice
@interactive_spice.setter
def interactive_spice(self,value):
self._interactive_spice=value
@property
def nproc(self):
"""int : Requested maximum number of threads for multithreaded simulations.
Eldo : maps to command line parameter '-nproc'
Spectre : maps to command line parameter '+mt'.
Ngspice : maps to 'set num_threads=' line in testbench.
"""
if hasattr(self,'_nproc'):
return self._nproc
else:
self._nproc=False
return self._nproc
@nproc.setter
def nproc(self,value):
self._nproc=value
# DSPF filenames
@property
def postlayout_subckts(self):
"""[str] : List containing filenames for subcircuit DSPF-files to be included for
post-layout simulations. The names given in this list are matched to dspf-files in
'./spice/' -directory. A postfix '.pex.dspf' is automatically appended
to the given names (this will probably change later).
Example
-------
::
self.postlayout_subckts = ['inv_v2','switch_v3']
would include files './spice/inv_v2.pex.dspf' and
'./spice/switch_v3.pex.dspf' as postlayout_subckts-files in the testbench. If top level
dspf (self.dspf) is given these are omitted. Otherwise simulator will replace
subcircuits with corresponding name with the postlayout netlist defined in the
dspf file.
"""
if not hasattr(self,'_postlayout_subckts'):
self._postlayout_subckts = []
return self._postlayout_subckts
@postlayout_subckts.setter
def postlayout_subckts(self,value):
self._postlayout_subckts=value
# DSPF filenames
@property
def dspf(self):
"""[str] : List containing filenames for DSPF-files to be included for post-layout
simulations. The names given in this list are matched to dspf-files in
'./spice/' -directory. A postfix '.pex.dspf' is automatically appended
to the given names (this will probably change later).
Example
-------
::
self.dspf = ['adc_top_level']
would include files './spice/adc_top_level.pex.dspf' as
a top level dspf-file in the testbench. If the
dspf-file contains definition matching the original design name of the
top-level netlist, it gets also renamed to match the module name
(dspf-file for top-level instance is possible).
"""
if not hasattr(self,'_dspf'):
self._dspf = []
return self._dspf
@dspf.setter
def dspf(self,value):
self._dspf=value
@property
def postlayout(self):
"""bool : Enables post-layout optimizations in the simulator command options.
(Default : False )
"""
if not hasattr(self,'_postlayout'):
if len(self.dspf) > 0:
self.print_log(type='I', msg = 'Setting postlayout to True due to given dspf-files')
self._postlayout = True
else:
self.print_log(type='O',
msg='In release v1.9, automatic postlayout simulation detection from netlist has been removed. This warning will be removed in coming releases.')
self.print_log(type='W',
msg='Postlayout attribute accessed before defined. Defaulting to False.')
self._postlayout=False
return self._postlayout
@postlayout.setter
def postlayout(self,value):
self._postlayout=value
@postlayout.deleter
def postlayout(self,value):
self._postlayout=None
@property
def iofile_eventdict(self):
"""dict : Dictionary to store event type output from the simulations. This should
speed up reading the results.
NOTE: Eldo seems to force output names to uppercase, let's
uppercase everything here to avoid key mismatches. (This should be changed).
"""
if not hasattr(self, '_iofile_eventdict'):
self._iofile_eventdict=dict()
for name, val in self.iofile_bundle.Members.items():
if (val.dir.lower()=='out' or val.dir.lower()=='output') and val.iotype=='event':
for key in val.ionames:
self._iofile_eventdict[key.upper()] = None
return self._iofile_eventdict
@iofile_eventdict.setter
def iofile_eventdict(self,val):
self._iofile_eventdict=val
@property
def dcsource_bundle(self):
"""Bundle : A thesdk.Bundle containing `spice_dcsource` objects. The `spice_dcsource`
objects are automatically added to this Bundle, nothing should be
manually added.
This is to automate biasing and operation conditions of the circuit.
"""
if not hasattr(self,'_dcsource_bundle'):
self._dcsource_bundle=Bundle()
return self._dcsource_bundle
@dcsource_bundle.setter
def dcsource_bundle(self,value):
self._dcsource_bundle=value
@property
def simcmd_bundle(self):
""" Bundle : A thesdk.Bundle containing `spice_simcmd` objects. The `spice_simcmd`
objects are automatically added to this Bundle, nothing should be
manually added.
"""
if not hasattr(self,'_simcmd_bundle'):
self._simcmd_bundle=Bundle()
return self._simcmd_bundle
@simcmd_bundle.setter
def simcmd_bundle(self,value):
self._simcmd_bundle=value
@property
def extracts(self):
""" Bundle : A thesdk.Bundle containing extracted quantities.
"""
return self.spice_simulator.extracts
@extracts.setter
def extracts(self,value):
self.spice_simulator.extracts = value
@property
def spice_submission(self):
"""str : Defines spice submission prefix from thesdk.GLOBALS['LSFSUBMISSION']
and thesdk.GLOBALS['LSFINTERACTIVE'] for LSF submissions.
Usually something like 'bsub -K' or 'bsub -I'.
"""
if not hasattr(self, '_spice_submission'):
try:
if not self.has_lsf:
self.print_log(type='I', msg='LSF not configured, running locally.')
self._spice_submission=''
else:
if self.interactive_spice:
if not self.distributed_run:
self._spice_submission = thesdk.GLOBALS['LSFINTERACTIVE'] + ' '
else: # Spectre LSF doesn't support interactive queues
self.print_log(type='W', msg='Cannot run in interactive mode if distributed mode is on!')
self._spice_submission = thesdk.GLOBALS['LSFSUBMISSION'] + ' -o %s/bsublog.txt ' % (self.spicesimpath)
else:
self._spice_submission = thesdk.GLOBALS['LSFSUBMISSION'] + ' -o %s/bsublog.txt ' % (self.spicesimpath)
except:
self.print_log(type='W',msg='Error while defining spice submission command. Running locally.')
self._spice_submission=''
return self._spice_submission
@spice_submission.setter
def spice_submission(self,value):
self._spice_submission=value
@property
def spicemisc(self):
""" [str] : List of manual commands to be pasted to the testbench. The strings are
pasted to their own lines (no linebreaks needed), and the syntax is
unchanged.
Example
-------
Setting initial voltages from testbench (Eldo)::
for i in range(nodes):
self.spicemisc.append('.ic NODE<%d> 0' % i)
The same example for Spectre::
self.spicemisc.append('simulator lang=spice')
for i in range(nodes):
self.spicemisc.append('.ic NODE<%d> 0' % i)
self.spicemisc.append('simulator lang=spectre')
"""
if not hasattr(self, '_spicemisc'):
self._spicemisc = []
return self._spicemisc
@spicemisc.setter
def spicemisc(self,value):
self._spicemisc = value
@property
def name(self):
"""str : Name of the module.
"""
if not hasattr(self, '_name'):
self._name=os.path.splitext(os.path.basename(self._classfile))[0]
return self._name
@property
def spicesrcpath(self):
"""str : Path to the spice source of the entity. Can be set manually to desired location.
This variable provides the dspf-parasitic netlist filepath.
( Default: <entity path>/spice )
"""
if not hasattr(self, '_spicesrcpath'):
self._spicesrcpath = self.entitypath + '/spice'
try:
if not (os.path.exists(self._spicesrcpath)):
os.makedirs(self._spicesrcpath)
self.print_log(type='I',msg='Creating %s.' % self._spicesrcpath)
except:
self.print_log(type='E',msg='Failed to create %s.' % self._spicesrcpath)
return self._spicesrcpath
@spicesrcpath.setter
def spicesrcpath(self,val):
self._spicesrcpath=val
@property
def spicesrc(self):
"""str : Path to the source netlist. Can be set manually to desired location.
( Default: 'spice/entityname.scs' )
N.B!:
Netlist has to contain the top-level design as a subcircuit definition!
"""
if not hasattr(self, '_spicesrc'):
self._spicesrc=self.spicesrcpath + '/' + self.name + self.spice_simulator.cmdfile_ext
if not os.path.exists(self._spicesrc):
self.print_log(type='W',msg='No source circuit found in %s.' % self._spicesrc)
return self._spicesrc
@spicesrc.setter
def spicesrc(self,value):
self._spicesrc = value
@property
def spicetbsrc(self):
"""str : Path to the spice testbench ('<spicesimpath>/tb_entityname.<suffix>').
This shouldn't be set manually.
"""
if not hasattr(self, '_spicetbsrc'):
self._spicetbsrc=self.spicesimpath + '/tb_' + self.name + self.spice_simulator.cmdfile_ext
return self._spicetbsrc
@property
def spicesubcktsrc(self):
"""str : Path to the parsed subcircuit file. ('<spicesimpath>/subckt_entityname.<suffix>').
This shouldn't be set manually.
"""
if not hasattr(self, '_spicesubcktsrc'):
self._spicesubcktsrc=self.spicesimpath + '/subckt_' + self.name + self.spice_simulator.cmdfile_ext
return self._spicesubcktsrc
@property
def plflag(self):
''' str : Postlayout simulation accuracy/RC reduction flag.
'''
if not hasattr(self, '_plflag'):
self._plflag=self.spice_simulator.plflag
return self._plflag
@plflag.setter
def plflag(self, val):
self.spice_simulator.plflag = val
self._plflag = val
@property
def spicecmd(self):
"""str : Simulation command string to be executed on the command line.
Automatically generated.
"""
if not hasattr(self,'_spicecmd'):
self._spicecmd = self.spice_simulator.spicecmd
return self._spicecmd
@spicecmd.setter
def spicecmd(self,value):
self.spice_simulator.spicecmd=value
@property
def spicedbpath(self):
"""str : Path to output waveform database. (<spicesimpath>/tb_<entityname>.<resultfile_ext>)
(For now only for spectre. HOW? should work for Eldo too)
"""
if not hasattr(self,'_spicedbpath'):
self._spicedbpath=self.spicesimpath+'/tb_'+self.name+self.spice_simulator.resultfile_ext
return self._spicedbpath
@spicedbpath.setter
def spicedbpath(self, value):
self._spicedbpath=value
### To be relocated
### These are simuator related i.e. ezwave does not work for ngspice.
@property
def plotprogram(self):
"""str : Sets the program to be used for visualizing waveform databases.
Options are ezwave (default) or viva.
"""
if not hasattr(self, '_plotprogram'):
self._plotprogram='ezwave'
return self._plotprogram
@plotprogram.setter
def plotprogram(self, value):
self._plotprogram=value
### End to be relocated
@property
def plotprogcmd(self):
""" str : Command to be run for interactive simulations.
"""
return self.spice_simulator.plotprogcmd
@plotprogcmd.setter
def plotprogcmd(self, value):
self.spice_simulator.plotprogcmd = value
@property
def save_database(self):
"""bool : Whether to save the waveform database (.wdb-file for eldo, raw-database
for spectre), when save_state=True.
( Default : False)
"""
if not hasattr(self,'_save_database'):
self._save_database=False
return self._save_database
@save_database.setter
def save_database(self,value):
self._save_database=value
@property
def save_output_file(self):
"""bool : If True and save_state is True, copy the output file of simulator
to entity statedir. Useful for scavenging results if simulator exited
but state was not written to disk for some reason.
( Default : False)
"""
if not hasattr(self, '_save_output_file'):
self._save_output_file=False
return self._save_output_file
@save_output_file.setter
def save_output_file(self, val):
self._save_output_file=val
@property
def load_output_file(self):
"""bool : Whether to load the outputs from simulator output file.
This only works if the file exists in the state directory, i.e.
the simulator was run with save_output_file=True.
WARNING: This will read the IOS from the output file, and REWRITE
THE ENTITY STATE on disk.
( Default : False )
"""
if not hasattr(self,'_load_output_file'):
self._load_output_file=False
return self._load_output_file
@load_output_file.setter
def load_output_file(self,value):
if value:
self.print_log(type='W', msg='load_output_file set to True! This will rewrite the entity state on disk!')
self._load_output_file=value
[docs]
def connect_spice_outputs(self):
"""Automatically called function to connect iofiles (outputs) to top
entity IOS Bundle items.
"""
for name,val in self.iofile_bundle.Members.items():
if val.dir == 'out':
self.IOS.Members[name].Data=self.iofile_bundle.Members[name].Data
[docs]
def read_spice_outputs(self):
"""Automatically called function to call read() functions of each
iofile with direction 'output'.
"""
first=True
for name, val in self.iofile_bundle.Members.items():
if val.dir.lower()=='out' or val.dir.lower()=='output':
if val.iotype=='event': # Event type outs are in same file, read only once to speed up things
if first:
self.iofile_bundle.Members[name].read()
first=False
self.check_output_accuracy(val.ionames[0]) # Time stamps are common to all, need to do only once
if len(val.ionames) == 1:
try:
if self.model == 'spectre':
if self.is_strobed:
self.iofile_bundle.Members[name].Data=self.filter_strobed(val.name,val.ionames[0])
else:
self.iofile_bundle.Members[name].Data=self.iofile_eventdict[val.ionames[0].upper()]
else:
self.iofile_bundle.Members[name].Data=self.iofile_eventdict[val.ionames[0].upper()]
except KeyError:
self.print_log(type='E',msg='Invalid ioname %s for iofile %s' % (val.ionames[0], name))
else: # Iofile is a bus?
data=[]
for i, key in enumerate(val.ionames):
try:
if i == 0:
# Parse the first member of bus
if self.model == 'spectre':
if self.is_strobed:
data=self.filter_strobed(val.name, key)
else:
data=self.iofile_eventdict[key.upper()]
else:
data=self.iofile_eventdict[key.upper()]
else:
# Next members are concatenated to array
if self.model == 'spectre' and self.is_strobed:
next=self.filter_strobed(val.name, key)
else:
next=self.iofile_eventdict[key.upper()]
try:
data=np.r_['1', data, next]
except ValueError:
self.print_log(type='W',msg='Invalid dimensions for concatenating arrays for IO %s!' % name)
except KeyError:
self.print_log(type='E', msg='Invalid ioname %s for iofile %s' % (key, name))
self.iofile_bundle.Members[name].Data=data
else:
self.iofile_bundle.Members[name].read()
elif val.dir.lower()=='output':
self.print_log(type='F',
msg='Direction indicator for %s of should be \'out\' and you are the one to fix your code.'
%(self.iofile_bundle.Members[name]))
[docs]
def execute_spice_sim(self):
"""Automatically called function to execute spice simulation.
"""
self.print_log(type='I', msg="Running external command %s" %(self.spicecmd) )
if os.system(self.spicecmd) > 0:
self.print_log(type='E', msg="Simulator (%s) returned non-zero exit code." % (self.model))
[docs]
def run_plotprogram(self):
''' Starting a parallel process for waveform viewer program.
The plotting program command can be set with 'plotprogram'.
Tested for spectre and eldo.
'''
if not self.distributed_run:
self.spice_simulator.run_plotprogram()
else:
self.print_log(type='I', msg='Waveform viewer %s not launched due to distributed run.' % self.plotprogram)
[docs]
def read_oppts(self):
""" Internally called function to read the DC operating points of the circuit
TODO: Implement for Eldo as well.
"""
self.spice_simulator.read_oppts()
@property
def spice_tb(self):
"""spice_module : Testbench instance. You can set the attributes of the testbench and dut below it before you execute run_spice.
if
Example
-------
::
self.spice_tb.dut.custom_subckt_name='custom_inverter'
self.run_spice()
"""
if not hasattr(self,'_spice_tb'):
if hasattr(self,'_tb'):
self.print_log(type='O',
msg='Propagating self.tb to self.spice_tb property. You should use spice_tb for spice simulations.')
self._spice_tb = self.tb
else:
self._spice_tb = stb(self)
return self._spice_tb
@spice_tb.setter
def spice_tb(self, value):
self._spice_tb = value
[docs]
def run_spice(self):
"""Externally called function to execute spice simulation.
"""
if self.load_state != '':
# Loading a previously stored state
if self.load_output_file:
self.read_spice_outputs()
self.connect_spice_outputs()
# Are these really something to be part of
# default execution
self.extract_powers()
self.read_oppts()
###
self._write_state()
else:
self._read_state()
else:
# Normal execution of full simulation
self.connect_spice_inputs()
self.spice_tb.generate_contents()
self.spice_tb.export(force=True)
self.write_spice_inputs()
if self.interactive_spice:
plotthread = threading.Thread(target=self.run_plotprogram,name='plotting')
plotthread.start()
self.execute_spice_sim()
self.read_spice_outputs()
self.connect_spice_outputs()
# Are these really something to be part of
# default execution
self.extract_powers()
self.read_oppts()
###
# Save entity state
if self.save_state:
self._write_state()
# If this is generic enough between simulators, it can be moved
# to thesdk (basically if spicedbpath has an equivalent in
# other simulators too)
if self.save_database:
try:
dbname = self.spicedbpath.split('/')[-1]
if os.path.isdir(self.spicedbpath):
shutil.copytree(self.spicedbpath,'%s/%s' % (self.statedir,dbname))
else:
shutil.copyfile(self.spicedbpath,'%s/%s' % (self.statedir,dbname))
self.print_log(msg='Saving waveform database to %s/%s' % (self.statedir,dbname))
except:
self.print_log(type='E',msg='Failed saving waveform database to %s/%s' % (self.statedir,dbname))
if self.save_output_file:
output_name = 'tb_%s.print' % self.name
filepath = self.spicesimpath
output_path = os.path.join(filepath, output_name)
targ_path = os.path.join(self.statedir, output_name)
shutil.copyfile(output_path, targ_path)
# Clean simulation results
self.delete_iofile_bundle()
self.delete_spicesimpath()
# Obsolete stuff
@property
def plotlist(self):
""" [ str ] : List of net names to be saved in the waveform database.
.. note::
Obsolete! Moved to `spice_simcmd` as a keyword argument.
"""
self.print_log(type='O', msg='Plotlist has been relocated as an argument to spice_simcmd!')
if not hasattr(self,'_plotlist'):
self._plotlist=[]
return self._plotlist
@plotlist.setter
def plotlist(self,value):
self.print_log(type='O', msg='Plotlist has been relocated as an argument to spice_simcmd!')
self._plotlist=value
@property
def errpreset(self):
"""str : Global accuracy parameter for Spectre simulations. Options include
'liberal', 'moderate' and 'conservative', in order of rising accuracy.
Example
-------
::
self.spice_simulator.errpreset='conservative'
"""
if not hasattr(self,'_errpreset'):
self._errpreset=self.spice_simulator.errpreset
self.print_log(type='O', msg='Errpreset is obsoleted as spectre specific command line argument')
self.print_log(type='O', msg='Alternative method for handling simulator specific command line arguments should be developed.')
return self.spice_simulator.errpreset
@errpreset.setter
def errpreset(self,value):
self.spice_simulator.errpreset=value