blob: aaf77fc5b4a813b22479ccce781842926e26addc [file] [log] [blame]
from configparser import ConfigParser
# Path to ngspice library
import os
import math
from spice import NgSpice, NgSpiceFile
from ngspice_read import ngspice_read
import numpy as np
import pandas as pd
from optimizer import NgOptimizer
import re
import sys
from tool_box import ProcessingHelper
import xarray as xr
import pickle
import time
import ast
from transistors import plotGmIdCurves
from configparser import ConfigParser
import gc
# xr.show_versions()
def example():
file_in='/home/simon/code/asic/analog/tia2/shahdoost.spice'
configfile='/home/simon/code/asic/analog/tia2/shahdoost.ini'
file_out='/home/simon/code/asic/analog/tia2/shahdoost_mod.spice'
# Read the config file
config = ConfigParser()
config.read(configfile)
parameters=config['Parameters']
# Generate a list parameters to be replaced
param={}
for k in parameters:
param[k]=parameters[k]
# Read the file
ngspice=NgSpice(file_in)
# Replace the parameters
replaceable=ngspice.findReplaceable()
for r in replaceable:
if not r.lower() in param.keys():
print(f'Warning: replaceable parameter found in spice but not present in .ini {r}')
ngspice.replace(param)
ngspice.write(file_out)
print (ngspice.pairs)
def operatingPoint():
'''
Opens the given spice file and replaces the *#OP# with statements for extracting the operating point.
'''
spiceFile='../analog/tia2/shahdoost_optim1_manual.spice'
outDir='../analog/tia2/op'
outFile=os.path.join(outDir, 'run.spice')
if not os.path.exists(outDir):
os.mkdir(outDir)
ngSpiceFile=NgSpiceFile(spiceFile)
ngSpiceFile.opExtraction(outDir)
ngSpiceFile.write(outFile)
def getAdditionalCoord(simu_result):
'''
Returns a list of user defined dimensions. User defined dimensions are dimensions which have a
name different than ['frequency', 'temp', 'model', 'mcrunno', 'spice_vector']
:returns: one_dim_coord, param_coord
'''
EXPECTED_DIMENSIONS=['frequency', 'temp', 'model', 'mcrunno', 'spice_vector']
one_dim_coord={}
param_coord={}
for dim in simu_result.dims:
if not dim in EXPECTED_DIMENSIONS:
if len(simu_result[dim])==1:
one_dim_coord[dim]=simu_result[dim].values
else:
param_coord[dim]=simu_result[dim].values
return one_dim_coord, param_coord
def setAxisFromConig(ax, config, ngvector):
'''
Helper function for plotting. Sets the axis properties based on configuration values.
'''
# If we get None as configuration, the full plot is not configured. In that case return immediately
if config is None:
return ax
# We expect that all vectors are configured.
if not ngvector in config:
raise RuntimeError('No configuration found for vector {}'.format(ngvector))
config=config[ngvector]
if not config is None:
if 'ylim_top' in config:
ax.set_ylim(top=config['ylim_top'])
if 'ylim_bottom' in config:
ax.set_ylim(bottom=config['ylim_bottom'])
if 'title' in config:
ax.set_title(config['title'])
if 'y_label' in config:
ax.set_ylabel(config['y_label'])
return ax
def plotOP(simu_result, outDir, simu_type, config):
'''
Generate plots for AC and NOISE analysis.
The following plots are generated
- x: frequency, y: AC analysis result, color: temperature.
'''
from matplotlib import pyplot as plt
print('Function plotOP')
spice_vectors=simu_result['spice_vector'].values
#temperatures=simu_result['temp'].values
#models=simu_result['model'].values
#freq=simu_result['frequency'].values
# Get rid of one dimensional coordinates
one_dim_coord, param_coord=getAdditionalCoord(simu_result)
coord=one_dim_coord
for ngvector in spice_vectors:
fig, ax = plt.subplots()
t=time.time()
plot_xr=simu_result.loc[{'spice_vector': ngvector}].to_dataset(name=ngvector)
#min_temp=min(temperatures)
#max_temp=max(temperatures)
plot_xr.plot.scatter(x='temp', y=ngvector, hue='model')
#, vmin=, vmax=85
print('Plot:', time.time()-t)
#ax.set_xscale("log")
ax.set_xlabel('Temperature / °C') # Add an x-label to the axes.
ax.set_ylabel(ngvector) # Add a y-label to the axes.
ax.set_title("{}".format(ngvector)) # Add a title to the axes.
ax.grid()
ax.set_ylim(bottom=0)
# Set the axis propoerties based on the configuration
ax=setAxisFromConig(ax, config, ngvector)
plt.tight_layout()
#ax.legend() # Add a legend.
#if ngvector=="v(v_vout)" or ngvector=="i(V0)":
# ax.set_ylim(ymin=0)
#
savePlot(plt, fig, simu_type, coord, ngvector, outDir)
def plotMeasurements(simu_result, outDir, simu_type, config):
'''
Generate plots for measurements.
The following plots are generated
- x: temperature, y: Measurement result, color: Process corner.
'''
from matplotlib import pyplot as plt
print('Function plotMeasurements')
spice_vectors=simu_result['spice_vector'].values
#temperatures=simu_result['temp'].values
#models=simu_result['model'].values
#freq=simu_result['frequency'].values
# Get rid of one dimensional coordinates
one_dim_coord, param_coord=getAdditionalCoord(simu_result)
coord_fn=one_dim_coord
if param_coord == {}:
param_coord[None]=[None]
for ngvector in spice_vectors:
for p_k, p_v in param_coord.items():
for v in p_v:
fig, ax = plt.subplots()
t=time.time()
coord={'spice_vector': ngvector}
if not p_k is None:
coord[p_k]=v
coord_fn[p_k]=v
plot_xr=simu_result.loc[coord].to_dataset(name=ngvector)
#min_temp=min(temperatures)
#max_temp=max(temperatures)
plot_xr.plot.scatter(x='temp', y=ngvector, hue='model')
#, vmin=, vmax=85
print('Plot:', time.time()-t)
ax.set_xlabel('Temperature / °C') # Add an x-label to the axes.
ax.set_ylabel(ngvector) # Add a y-label to the axes.
ax.set_title("{}".format(ngvector)) # Add a title to the axes.
# Set the axis properties based on the configuration
ax=setAxisFromConig(ax, config, ngvector)
ax.grid()
plt.tight_layout()
#ax.legend() # Add a legend.
#if ngvector=="v(v_vout)" or ngvector=="i(V0)":
# ax.set_ylim(ymin=0)
#
savePlot(plt, fig, simu_type, coord_fn, ngvector, outDir)
def plotAC(simu_result, outDir, simu_type, config):
'''
Generate plots for AC and NOISE analysis.
The following plots are generated
- x: frequency, y: AC analysis result, color: temperature.
'''
from matplotlib import pyplot as plt
print('Function plotAC')
spice_vectors=simu_result['spice_vector'].values
temperatures=simu_result['temp'].values
#models=simu_result['model'].values
#freq=simu_result['frequency'].values
# Get rid of one dimensional coordinates
one_dim_coord, param_coord=getAdditionalCoord(simu_result)
coord=one_dim_coord
for ngvector in spice_vectors:
fig, ax = plt.subplots()
t=time.time()
plot_xr=simu_result.loc[{'spice_vector': ngvector}].to_dataset(name=ngvector)
#min_temp=min(temperatures)
#max_temp=max(temperatures)
plot_xr.plot.scatter(x='frequency', y=ngvector, hue='temp', cmap='coolwarm', s=3, alpha=0.3, cbar_kwargs={'label': 'Temperature / °C'})
#, vmin=, vmax=85
print('Plot:', time.time()-t)
ax.set_xscale("log")
ax.set_xlabel('Frequency / Hz') # Add an x-label to the axes.
ax.set_ylabel(ngvector) # Add a y-label to the axes.
ax.set_title("{}".format(ngvector)) # Add a title to the axes.
# Set the axis propoerties based on the configuration
ax=setAxisFromConig(ax, config, ngvector)
ax.grid()
plt.tight_layout()
#ax.legend() # Add a legend.
#if ngvector=="v(v_vout)" or ngvector=="i(V0)":
# ax.set_ylim(ymin=0)
#
savePlot(plt, fig, simu_type, coord, ngvector, outDir)
def plotTran(simu_result, outDir, simu_type, config):
'''
Generate plots for AC and NOISE analysis.
The following plots are generated
- x: frequency, y: AC analysis result, color: temperature.
'''
from matplotlib import pyplot as plt
print('Function plotTran')
spice_vectors=simu_result['spice_vector'].values
temperatures=simu_result['temp'].values
#models=simu_result['model'].values
#freq=simu_result['frequency'].values
# Get rid of one dimensional coordinates
one_dim_coord, param_coord=getAdditionalCoord(simu_result)
coord=one_dim_coord
for ngvector in spice_vectors:
fig, ax = plt.subplots()
t=time.time()
plot_xr=simu_result.loc[{'spice_vector': ngvector}].to_dataset(name=ngvector)
#min_temp=min(temperatures)
#max_temp=max(temperatures)
plot_xr.plot.scatter(x='time', y=ngvector, hue='temp', cmap='coolwarm', s=3, alpha=0.3, cbar_kwargs={'label': 'Temperature / °C'})
#, vmin=, vmax=85
print('Plot:', time.time()-t)
# ax.set_xscale("log")
ax.set_xlabel('Time / s') # Add an x-label to the axes.
ax.set_ylabel(ngvector) # Add a y-label to the axes.
ax.set_title("{}".format(ngvector)) # Add a title to the axes.
# Set the axis propoerties based on the configuration
ax=setAxisFromConig(ax, config, ngvector)
ax.grid()
plt.tight_layout()
#ax.legend() # Add a legend.
#if ngvector=="v(v_vout)" or ngvector=="i(V0)":
# ax.set_ylim(ymin=0)
#
savePlot(plt, fig, simu_type, coord, ngvector, outDir)
def savePlot(plt, fig, simu_type, coord, ngvector, outDir):
'''
Saves the plot. Used by plotAC, plotMeasurements etc. to avoid redundant code.
'''
t=time.time()
fname_base="{}_".format(simu_type)
for k, v in coord.items():
fname_base+="{}_{}_".format(k, v)
fname_base+="{}".format(ngvector)
file=os.path.join(outDir, fname_base+".svg")
image_format = 'svg' # e.g .png, .svg, etc.
#fig.savefig(file, format=image_format, dpi=1200)
file=os.path.join(outDir, fname_base+".png")
file_small=os.path.join(outDir, fname_base+"_small.png")
image_format = 'png' # e.g .png, .svg, etc.
fig.savefig(file, format=image_format, dpi=1200)
fig.savefig(file_small, format=image_format, dpi=300)
plt.close(fig)
print('Save plot:', time.time()-t)
def investigateCorners(workingDir, spiceFile, voltages, corners, maxCpu, plot_config=None, param={}, mc=True):
'''
Replaces #model# by the name of a simulation model and investigates the corners.
'''
# Operating voltages
# Typically troublesome corners
#corners=["tt"]#""sf", "tt", "fs"]
# Device mismatch corners
if mc:
corners_mm=[c+'_mm' for c in corners]
run=True
# Run spice
ngspice_instances=[]
for voltage in voltages:
outDir=os.path.join(workingDir, 'corners_{}V'.format(voltage))
if not os.path.exists(outDir):
os.mkdir(outDir)
spiceParam=param
spiceParam['model']=corners_mm
spiceParam['UB']= voltage
# Run spice
#xr_file="ngspice_small_v{}.xr".format(voltage)
pickleFile="ngspice_rawres_v{}.pickle".format(voltage)
pickleFile=os.path.join(workingDir, pickleFile)
ngspice=NgSpice(spiceFile, outDir, parallel=True, max_cpus=maxCpu)
if run:
result=ngspice.run(spiceParam, delete=True)
ngspice.savePickle(pickleFile)
else:
ngspice.loadPickle(pickleFile)
ngspice_instances.append(ngspice)
# Convert to xarray
# We limit ourselves to 4 cores to not use excessive memory
p=ProcessingHelper(parallel=True, maxCpu=4)
for ngspice in ngspice_instances:
p.add(ngspice.getResultAsDataArray, [])
results_xr=p.run()
# Free memory
for ngspice in ngspice_instances:
del ngspice
del ngspice_instances
gc.collect()
# Plot
# We limit ourselves to 4 cores to not use excessive memory
# TODO: use multiprocessing.shared_memory to make out xarrays accessible to all
# processes, this should enable a significant speedup.
p=ProcessingHelper(parallel=True, maxCpu=4)
for result_xr in results_xr:
if False:
pickleFileXr="ngspice_small_xr_v{}.pickle".format(voltage)
if run:
with open(pickleFileXr, 'wb') as file:
pickle.dump(result_xr, file)
else:
with open(pickleFile, 'rb') as file:
result_xr=pickle.load(file)
# Plot measurements
for simu_type in result_xr.keys():
for simu_name in result_xr[simu_type]:
print(simu_type, simu_name)
simu_result=result_xr[simu_type][simu_name]
outDir="{}_{}".format(simu_type, simu_name)
if not outDir.lower() in plot_config:
raise(RuntimeError('Plot configuration missing for {}'.format(outDir.lower())))
config_thisplot=plot_config[outDir.lower()]
outDir=os.path.join(workingDir, outDir)
if not os.path.exists(outDir):
os.mkdir(outDir)
if simu_type.upper() == 'MEAS':
p.add(plotMeasurements, [simu_result, outDir, simu_type, config_thisplot])
elif simu_type.upper() == 'AC':
p.add(plotAC, [simu_result, outDir, simu_type, config_thisplot])
elif simu_type.upper() == 'NOISE':
p.add(plotAC, [simu_result, outDir, simu_type, config_thisplot])
elif simu_type.upper() == 'OP':
p.add(plotOP, [simu_result, outDir, simu_type, config_thisplot])
elif simu_type.upper() == 'TRAN':
p.add(plotTran, [simu_result, outDir, simu_type, config_thisplot])
else:
raise(RuntimeError('Unrecognized simulation type {}'.format(simu_type)))
#result_ds=xr.Dataset.from_dict(result_xr)
#result_xr=result_ds.to_dict()
# xr_list=[]
# xr_file_list=[]
# for simu_type in result_xr.keys():
# for simu_name in result_xr[simu_type]:
# xr_file="ngspice_{}_{}_v{}.xr".format(simu_type, simu_name, voltage)
# xr_file=os.path.join(workingDir, xr_file)
# xr_file_list.append(xr_file)
# result_ds=result_xr[simu_type][simu_name].to_dataset(name=f"{simu_type}_{simu_name}")
# xr_list.append(result_ds)
#
# xr.save_mfdataset(xr_list, xr_file_list)
#
#
# for simu_type in result_xr.keys():
# for simu_name in result_xr[simu_type]:
# xr_file="ngspice_{}_{}_v{}.xr".format(simu_type, simu_name, voltage)
# xr_file=os.path.join(workingDir, xr_file)
# xr_file_list.append(xr_file)
# result_ds=result_xr[simu_type][simu_name].to_dataset(name=f"{simu_type}_{simu_name}")
# xr_list.append(result_ds)
#
# xr.save_mfdataset(xr_list, xr_file_list)
#
print('Now starting parallel plotting')
p.run()
print("Done!")
class AcSimulationResult:
'''
Provides access to AC simulation results and helper functions for plotting.
'''
def __init__(self, simulationResult):
'''
:param simulationResult.
'''
pass
def getParameters(self):
'''
Returns the parameters supplied to the simulation as data frame. This also includes the temperature.
'''
pass
def getReslutByParameter(self):
pass
def main_raw():
file='../analog/transistors/test_nmos3.raw'
ngread=ngspice_read(file)
print('Breakpoint')
def test_tia():
workingDirBase='../analog/test/'
# spiceFile=os.path.join(workingDir, 'test_bandgap_mc.spice')
spiceFile=os.path.join(workingDirBase, 'test_tia_rgc_full_mc.spice')
darkcur = [-5e-6, -2.5e-6, 0, 2.5e-6, 5e-6]
#, -2.5e-6, 0, 2.5e-6, 5e-6]
# for darkcur in [-5e-6, -2.5e-6, 0, 2.5e-6, 5e-6]:
param={'ID': darkcur}
workingDir= os.path.join(workingDirBase, 'wd')
if not os.path.exists(workingDir):
os.mkdir(workingDir)
investigateCorners(workingDir, spiceFile, param)
def investigateIni():
'''
Investigate corners using parameters given in an ini file.
'''
if len(sys.argv) != 2:
print('Wrong arguments. Usage: investigate.py iniFile.')
quit()
parser=ConfigParser()
parser.read(sys.argv[1])
# Read files section and assemble pathes
baseDir=ast.literal_eval(parser['Files']['baseDirectory'])
spiceFile=ast.literal_eval(parser['Files']['spiceFile'])
spiceFile=os.path.join(baseDir, spiceFile)
outDir=ast.literal_eval(parser['Files']['outputDirectory'])
outDir= os.path.join(baseDir, outDir)
# Read voltages and corners
voltages=ast.literal_eval(parser['Setup']['voltages'])
corners=ast.literal_eval(parser['Setup']['corners'])
maxCpu=ast.literal_eval(parser['Setup']['maxCpus'])
plot_config={}
for section in parser['Plot']:
value=parser['Plot'][section]
plot_config[section]=ast.literal_eval(value)
# Create the output directory
if not os.path.exists(outDir):
os.mkdir(outDir)
param={}
for k,v in parser['Parameter'].items():
param[k.upper()]=ast.literal_eval(v)
investigateCorners(outDir, spiceFile, voltages, corners, maxCpu, plot_config, param)
print('Done')
def test_iref():
mode='OP'
workingDirBase='../analog/test/'
# spiceFile=os.path.join(workingDir, 'test_bandgap_mc.spice')
spiceFile=os.path.join(workingDirBase, 'test_low_pvt_source_mc.spice')
workingDir= os.path.join(workingDirBase, 'isource')
if not os.path.exists(workingDir):
os.mkdir(workingDir)
investigateCorners(workingDir, spiceFile, mode)
if __name__ == "__main__":
investigateIni()
#task='MC'
#
#if task == 'MC':
# test_iref()
#test_tia()
#MODE='OP'
# elif task == 'plotOP':
# TRANSISTOR_TYPES=['nfet_01v8_lvt', 'pfet_01v8_lvt', 'pfet_01v8_hvt', 'nfet_01v8', 'pfet_01v8']
# for t in TRANSISTOR_TYPES:
# plotGmIdCurves(t, 0.4)
#operatingPoint()
# optimize2()