"""
=======
Spectre
=======
Spectre simulation interface package for Spectre for TheSyDeKick.
Initially written by Okko Järvinen, 2019
"""
import os
import sys
from abc import *
from thesdk import *
from spice.spice_common import *
import numpy as np
[docs]
class spectre(spice_common):
"""This class is used as instance in spice_simulatormodule property of
spice class. Contains simulator dependent definitions.
Parameters
----------
parent: object, None (mandatory to define). TheSyDeKick parent entity object for this simulator class.
**kwargs :
None
"""
def __init__(self, parent=None,**kwargs):
if parent==None:
self.print_log(type='F', msg="Parent of simulator module not given")
else:
self.parent=parent
@property
def syntaxdict(self):
self.print_log(type='O', msg='Syntaxdict is obsoleted. Access properties directly')
self._syntaxdict = {
"cmdfile_ext" : self.cmdfile_ext,
"resultfile_ext" : self.resultfile_ext,
"commentchar" : self.commentchar,
"commentline" : self.commentline,
"nprocflag" : self.nprocflag,
"simulatorcmd" : self.simulatorcmd,
"dcsource_declaration" : self.dcsource_declaration,
"parameter" : self.parameter,
"option" : self.option,
"include" : self.include,
"dspfinclude" : self.dspfinclude,
"subckt" : self.subckt,
"lastline" : self.lastline,
"eventoutdelim" : self.eventoutdelim, # Two spaces
"csvskip" : self.csvskip
}
return self._syntaxdict
@syntaxdict.setter
def syntaxdict(self,value):
self._syntaxdict=value
@property
def cmdfile_ext(self):
"""Extension of the command file : str
"""
return '.scs'
@property
def resultfile_ext(self):
"""Extension of the result file : str
"""
return '.raw'
@property
def commentchar(self):
"""Comment character of the simulator : str
"""
return '//'
@property
def commentline(self):
"""Comment line for the simulator : str
"""
return '///////////////////////\n'
@property
def nprocflag(self):
"""String for defining multithread execution : str
"""
return '+mt='
@property
def simulatorcmd(self):
"""Simulator execution command : str
"""
return 'spectre -64 +lqtimeout=0 ++aps=%s' %(self.errpreset)
@property
def dcsource_declaration(self):
"""DC source declaration : str
"""
#self.print_log(type='F', msg='DC source declaration not defined for ngspice')
return 'vsource type=dc dc='
@property
def parameter(self):
"""Netlist parameter definition string : str
"""
return 'parameters'
@property
def option(self):
"""Netlist option definition string : str
"""
return 'options'
@property
def include(self):
"""Netlist include string : str
"""
return 'include'
@property
def dspfinclude(self):
"""Netlist dspf-file include string : str
"""
return 'dspf_include'
@property
def subckt(self):
"""Subcircuit include string : str
"""
return 'subckt'
@property
def lastline(self):
"""Last line of the simulator command file : str
"""
return '///'
@property
def eventoutdelim(self):
"""Delimiter for the events : str
"""
return ','
@property
def csvskip(self):
"""Needs documentation. Lines skipped in result file : int
"""
return 0
@property
def plflag_simcmd_prefix(self):
"""
Simulator specific prefix for enabling postlayout optimization
Postfix comes from self.plflag (user defined)
"""
if not hasattr(self, '_plflag_simcmd_prefix'):
self._plflag_simcmd_prefix="+postlayout="
return self._plflag_simcmd_prefix
@property
def plflag(self):
'''
Postlayout simulation accuracy/RC reduction flag.
See: https://community.cadence.com/cadence_blogs_8/b/cic/posts/spectre-optimizing-spectre-aps-performance
'''
if not hasattr(self, '_plflag'):
self._plflag=f"upa"
return self._plflag
@plflag.setter
def plflag(self, val):
if val in ["upa", "hpa"]:
self._plflag=val
else:
self.print_log(type='W', msg='Unsupported postlayout flag: %s' % val)
@property
def errpreset(self):
""" String
Global accuracy parameter for Spectre simulations. Options include
'liberal', 'moderate' and 'conservative', in order of rising accuracy.
You can set this by accesssing spice langmodule
Example
-------
self.spice_langmodule.errpreset='conservative'
"""
if not hasattr(self,'_errpreset'):
self._errpreset='moderate'
return self._errpreset
@errpreset.setter
def errpreset(self,value):
self._errpreset=value
@property
def plotprogram(self):
""" String
Sets the program to be used for visualizing waveform databases.
Options are ezwave (default) or viva.
"""
if not hasattr(self, '_plotprogram'):
if hasattr(self.parent,'plotprogram'):
self._plotprogram=self.parent.plotprogram
else:
self._plotprogram='ezwave'
return self._plotprogram
@plotprogram.setter
def plotprogram(self, value):
if value not in [ 'ezwave', 'viva' ]:
self.print_log(type='F',
msg='%s not supported for plotprogram, only ezvave and viva are supported')
else:
self._plotprogram = value
@property
def plotprogcmd(self):
""" str : Command to be run for interactive simulations.
"""
if self.plotprogram == 'ezwave':
self._plotprogcmd='%s -MAXWND -LOGfile %s/ezwave.log %s &' % \
(self.plotprogram,self.parent.spicesimpath,self.parent.spicedbpath)
elif self.plotprogram == 'viva':
self._plotprogcmd='%s -datadir %s -nocdsinit &' % \
(self.plotprogram,self.parent.spicedbpath)
else:
self.print_log(type='F',msg='Unsupported plot program \'%s\'.' % self.plotprogram)
return self._plotprogcmd
@plotprogcmd.setter
def plotprogcmd(self, value):
self._plotprogcmd=value
@property
def spicecmd(self):
"""String
Simulation command string to be executed on the command line.
Automatically generated.
"""
if not hasattr(self,'_spicecmd'):
if self.parent.nproc:
nprocflag = "%s%d" % (self.nprocflag,self.parent.nproc)
self.print_log(type='I',msg='Enabling multithreading \'%s\'.' % nprocflag)
else:
nprocflag = ""
if self.parent.postlayout:
plflag=f"{self.plflag_simcmd_prefix}{self.plflag}"
self.print_log(type='I',msg='Enabling post-layout optimization \'%s\'.' % plflag)
else:
plflag = ''
spicesimcmd = (self.simulatorcmd + " %s %s -outdir %s "
% (plflag,nprocflag,self.parent.spicesimpath))
self._spicecmd = self.parent.spice_submission+spicesimcmd+self.parent.spicetbsrc
return self._spicecmd
[docs]
def run_plotprogram(self):
''' Starting a parallel process for waveform viewer program.
The plotting program command can be set with 'plotprogram' property.
'''
tries = 0
while tries < 100:
if os.path.exists(self.parent.spicedbpath):
# More than just the logfile exists
if len(os.listdir(self.parent.spicedbpath)) > 1:
# Database file has something written to it
filesize = []
for f in os.listdir(self.parent.spicedbpath):
filesize.append(os.stat('%s/%s' % (self.parent.spicedbpath,f)).st_size)
if all(filesize) > 0:
break
else:
time.sleep(2)
tries += 1
cmd=self.plotprogcmd
self.print_log(type='I', msg='Running external command: %s' % cmd)
try:
ret=os.system(cmd)
if ret != 0:
self.print_log(type='W', msg='%s returned with exit status %d.' % (self.plotprogram, ret))
except:
self.print_log(type='W',msg='Something went wrong while launcing %s.' % self.plotprogram)
self.print_log(type='W',msg=traceback.format_exc())
[docs]
def read_oppts(self):
""" Internally called function to read the DC operating points of the circuit
TODO: Implement for Eldo as well.
"""
try:
if 'dc' in self.parent.simcmd_bundle.Members.keys():
self.extracts.Members.update({'oppts' : {}})
sweep=False
# Get dc simulation file name
for name, val in self.parent.simcmd_bundle.Members.items():
mc = val.mc
if name == 'dc':
fname=''
if len(val.sweep) != 0:
for i in range(0, len(val.sweep)):
sweep=True
fname += 'Sweep%d-[0-9]*_' % i
if mc:
fname+='mc_oppoint.dc'
else:
fname+='oppoint.dc'
else:
if mc:
fname = 'mc_oppoint*.dc'
else:
fname = 'oppoint*.dc'
break
# For distributed runs
if self.parent.distributed_run:
path=os.path.join(self.parent.spicesimpath,'tb_%s.raw' % self.parent.name, '[0-9]*',
fname)
else:
path=os.path.join(self.parent.spicesimpath,'tb_%s.raw' % self.parent.name, fname)
# Sort files so that sweeps are in correct order
if sweep:
num_sweeps = len(val.sweep)
for i in range(num_sweeps):
files = sorted(glob.glob(path),key=lambda x: self.sorter(x, i))
else:
files = glob.glob(path)
if len(files)>1:# This shoudln't happen
self.print_log(type='W', msg='DC analysis was not a sweep, but multiple output files were found! Results may be in incorrect order!')
valbegin = 'VALUE\n'
eof = 'END\n'
parsevals = False
for file in files:
with open(file, 'r') as f:
for line in f:
if line == valbegin: # Scan file until unit descriptions end and values start
parsevals = True
elif line != eof and parsevals: # Scan values from output until EOF
line = line.replace('\"', '')
parts = line.split()
if len(parts) >= 3:
if ':' in parts[0]: # This line contains op point parameter (e.g. vgs)
dev, param = parts[0].split(':')
elif ':' not in parts[0] and parts[1] == 'V': # This is a node voltage
dev = parts[0]
param = parts[1]
val = float(parts[2])
if dev not in self.extracts.Members['oppts']: # Found new device
self.extracts.Members['oppts'].update({dev : {}})
if param not in self.extracts.Members['oppts'][dev]: # Found new parameter for device
self.extracts.Members['oppts'][dev].update({param : [val]})
else: # Parameter already existed, just append value. This can occur in e.g. sweeps
self.extracts.Members['oppts'][dev][param].append(val)
elif line == eof:
parsevals = False
except:
self.print_log(type='W', msg=traceback.format_exc())
self.print_log(type='W',msg='Something went wrong while extracting DC operating points.')