Source code for spice.testbench

"""
===============
Spice Testbench
===============

Testbench generation class for spice simulations.

"""
import os
import sys
import subprocess
import shlex
import fileinput
import shutil
from thesdk import *
from spice.testbench_common import testbench_common
from spice.ngspice.ngspice_testbench import ngspice_testbench
from spice.eldo.eldo_testbench import eldo_testbench
from spice.spectre.spectre_testbench import spectre_testbench
from spice.spice_module import spice_module
import pdb

import numpy as np
import pandas as pd
from functools import reduce
import textwrap
from datetime import datetime

[docs] class testbench(testbench_common): """ This class generates all testbench contents. This class is utilized by the main spice class. """ def __init__(self, parent=None, **kwargs): """ Executes init of testbench_common, thus having the same attributes and parameters. Parameters ---------- **kwargs : See module testbench_common """ #This should be language specific. super().__init__(parent=parent,**kwargs) self.parent=parent self.model=self.parent.model @property def DEBUG(self): """ This fixes DEBUG prints in spice_iofile, by propagating the DEBUG flag of the parent entity. """ return self.parent.DEBUG @property def testbench_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_testbench :type: eldo_testbench :type: spectre_testbench """ if not hasattr(self,'_testbench_simulator'): if self.model == 'ngspice': self._testbench_simulator=ngspice_testbench(parent=self.parent) if self.model == 'eldo': self._testbench_simulator=eldo_testbench(parent=self.parent) if self.model == 'spectre': self._testbench_simulator=spectre_testbench(parent=self.parent) return self._testbench_simulator @property def dut(self): """ Design under test :type: spice_module """ if not hasattr(self,'_dut'): self._dut = spice_module(file=self._dutfile,parent=self.parent) return self._dut # Generating eldo/spectre parameters string @property def parameters(self): """String Spice parameters string parsed from self.spiceparameters -dictionary in the parent entity. """ if not hasattr(self,'_parameters'): self._parameters = "%s Parameters\n" % self.parent.spice_simulator.commentchar for parname,parval in self.parent.spiceparameters.items(): self._parameters += self.parent.spice_simulator.parameter + ' ' + str(parname) + "=" + str(parval) + "\n" return self._parameters @parameters.setter def parameters(self,value): self._parameters=value @parameters.deleter def parameters(self,value): self._parameters=None # Generating eldo/spectre library inclusion string @property def libcmd(self): """str : Library inclusion string. Parsed from self.spicecorner -dictionary in the parent entity, as well as 'ELDOLIBFILE' or 'SPECTRELIBFILE' global variables in TheSDK.config. """ return self.testbench_simulator.libcmd @libcmd.setter def libcmd(self,value): self.testbench_simulator.libcmd = value # Generating netlist inclusion string @property def includecmd(self): """String Subcircuit inclusion string pointing to generated subckt_* -file. """ if not hasattr(self,'_includecmd'): self._includecmd = "%s Subcircuit file\n" % self.parent.spice_simulator.commentchar self._includecmd += "%s \"%s\"\n" % (self.parent.spice_simulator.include,self._subcktfile) return self._includecmd @includecmd.setter def includecmd(self,value): self._includecmd=value @includecmd.deleter def includecmd(self,value): self._includecmd=None
[docs] def copy_dspf(self): try: for cell in self.parent.dspf: src = os.path.join(self.parent.spicesrcpath, '%s.pex.dspf' % cell) dest = os.path.join(self.parent.spicesimpath, '%s.pex.dspf' % cell) shutil.copy(src, dest) except: self.print_log(type='F',msg='Could not copy DSPF for cell %s to %s' %(cell, dest)) self.print_log(type='F',msg=traceback.format_exc())
# DSPF include commands @property def dspfincludecmd(self): """String DSPF-file inclusion string pointing to files corresponding to self.dspf in the parent entity. """ if not hasattr(self,'_dspfincludecmd'): if len(self.parent.dspf) > 0: self.copy_dspf() self.print_log(type='I',msg='Including exctracted parasitics from DSPF.') self._dspfincludecmd = "%s Extracted parasitics\n" % self.parent.spice_simulator.commentchar origcellmatch = re.compile(r"DESIGN") for cellname in self.parent.dspf: dspfpath = '%s/%s.pex.dspf' % (self.parent.spicesimpath,cellname) try: found = False with open(dspfpath) as dspffile: lines = dspffile.readlines() for line in lines: # This mathch only check if there is a DESIGN in dpsf file. if origcellmatch.search(line) != None: words = line.split() cellname = words[-1].replace('\"','') if cellname.lower() == self.parent.name.lower(): self.print_log(type='I',msg='Found DSPF cell name matching to original top-level cell name.') found = True self._origcellname=cellname elif cellname.lower() == self.dut.custom_subckt_name.lower(): self.print_log(type='I',msg='Found DSPF cellname matching to custom_subckt_name: %s.' % cellname) found = True self._origcellname=cellname # Break looping once found break if found: # Match is case insensitive, we will rename for perfect match. self.print_log(type='I',msg='Renaming DSPF top cell name accordingly from "%s" to "%s".' % (cellname,self.parent.name)) with fileinput.FileInput(dspfpath,inplace=True,backup='.bak') as f: for line in f: print(line.replace(self._origcellname,self.parent.name),end='') else: self.print_log(type='F',msg='No DESIGN string in DSPF matching %s or %s. Aborting' %(self.parent.name, self.dut.custom_subckt_name)) self.print_log(type='I',msg='Including DSPF-file: %s' % dspfpath) self._dspfincludecmd += "%s \"%s\"\n" % (self.parent.spice_simulator.dspfinclude,dspfpath) except: self.print_log(type='F',msg='No DESIGN string in DSPF matching %s or %s. Aborting' %(self.parent.name, self.dut.custom_subckt_name)) self.print_log(type='F',msg=traceback.format_exc()) else: self._dspfincludecmd = '' if len(self.parent.postlayout_subckts) > 0: self.print_log(type='I',msg='Including exctracted parasitics from subcircuit DSPF.') self._dspfincludecmd = "%s Extracted subcircuit parasitics\n" % self.parent.spice_simulator.commentchar for dspf in self.parent.postlayout_subckts: dspfpath = '%s/%s.pex.dspf' % (self.parent.spicesrcpath,dspf) if os.path.exists(dspfpath): self.print_log(type='I',msg='Including subcircuit DSPF-file: %s' % dspfpath) self._dspfincludecmd += "%s \"%s\"\n" % (self.parent.spice_simulator.dspfinclude,dspfpath) else: self.print_log(type='W',msg='No such file or directory %s.'%dspfpath) return self._dspfincludecmd @dspfincludecmd.setter def dspfincludecmd(self,value): self._dspfincludecmd=value @dspfincludecmd.deleter def dspfincludecmd(self,value): self._dspfincludecmd=None @property def misccmd(self): """String Miscellaneous command string corresponding to self.spicemisc -list in the parent entity. """ if not hasattr(self,'_misccmd'): self._misccmd="%s Manual commands\n" % (self.parent.spice_simulator.commentchar) mcmd = self.parent.spicemisc for cmd in mcmd: self._misccmd += cmd + "\n" return self._misccmd @misccmd.setter def misccmd(self,value): self._misccmd=value @misccmd.deleter def misccmd(self,value): self._misccmd=None @property def dcsourcestr(self): """str : DC source definitions parsed from spice_dcsource objects instantiated in the parent entity. """ return self.testbench_simulator.dcsourcestr @dcsourcestr.setter def dcsourcestr(self,value): self.testbench_simulator.dcsourcestr = value @property def inputsignals(self): """str : Input signal definitions parsed from spice_iofile objects instantiated in the parent entity. """ return self.testbench_simulator.inputsignals @property def simcmdstr(self): """str : Simulation command definition parsed from spice_simcmd object instantiated in the parent entity. """ return self.testbench_simulator.simcmdstr # Generating plot and print commands @property def plotcmd(self): """str : All output IOs are mapped to plot or print statements in the testbench. Also manual plot commands through `spice_simcmd.plotlist` are handled here. """ return self.testbench_simulator.plotcmd
[docs] def export(self,**kwargs): """ Internally called function to write the testbench to a file. Parameters ---------- force : Bool, False """ force=kwargs.get('force', False) if self.parent.postlayout and len(self.parent.dspf) == 0 and len(self.parent.postlayout_subckts) == 0: self.print_log(type='W',msg='No top-cell dspf or subcircuit dspf for postlayout simulation. Are you using some other netlist format?') self.print_log(type='W',msg=f'Exporting subcircuit, but simulation is not postlayout unless {self.parent.spicesrc} is postlayout netlist!') self.dut.export_subckts(file=self._subcktfile, force=force) if not os.path.isfile(self.file): self.print_log(type='D',msg='Exporting spice testbench to %s' %(self.file)) with open(self.file, "w") as module_file: module_file.write(self.contents) elif os.path.isfile(self.file) and not kwargs.get('force'): self.print_log(type='F', msg=('Export target file %s exists.\n Force overwrite with force=True.' %(self.file))) elif kwargs.get('force'): self.print_log(type='I',msg='Forcing overwrite of spice testbench to %s.' %(self.file)) with open(self.file, "w") as module_file: module_file.write(self.contents)
[docs] def generate_contents(self): """ Internally called function to generate testbench contents. """ self.contents = (self.header + "\n" + self.libcmd + "\n" + self.includecmd + "\n" + self.dspfincludecmd + "\n" + self.options + "\n" + self.parameters + "\n" + self.dut.instance + "\n\n" + self.misccmd + "\n" + self.dcsourcestr + "\n" + self.inputsignals + "\n" + self.simcmdstr + "\n" + self.plotcmd + "\n" + self.parent.spice_simulator.lastline+"\n")