Add plotting scripts
diff --git a/scripts/.ztr-directory b/scripts/.ztr-directory new file mode 100644 index 0000000..f5acee9 --- /dev/null +++ b/scripts/.ztr-directory
@@ -0,0 +1 @@ +{"sorting":"name-up","project":null,"icon":null} \ No newline at end of file
diff --git a/scripts/esd_clamp.ini b/scripts/esd_clamp.ini new file mode 100644 index 0000000..66b7346 --- /dev/null +++ b/scripts/esd_clamp.ini
@@ -0,0 +1,19 @@ +[Setup] +maxCpus=10 +voltages=[1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5] +corners=["tt", "sf", "ff", "fs", "ss", "ll", "hh", "hl", "lh"] + +[Files] +baseDirectory='../analog/test/' +spiceFile='test_esd_mc.spice' +outputDirectory='esd_clamp' + +[Parameter] +; ID=[-20e-6, -10e-6, 0, 10e-6, 20e-6] +; FB_DIS=[0] +; FB_PP_EN=[0] + +[Plot] +tran_tran=None +op_op=None +
diff --git a/scripts/investigate.py b/scripts/investigate.py new file mode 100644 index 0000000..aaf77fc --- /dev/null +++ b/scripts/investigate.py
@@ -0,0 +1,548 @@ + +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()
diff --git a/scripts/isource.ini b/scripts/isource.ini new file mode 100644 index 0000000..9d8f261 --- /dev/null +++ b/scripts/isource.ini
@@ -0,0 +1,14 @@ +[Setup] +maxCpus=15 +voltages=[1.7, 1.8, 1.9] +corners=["tt", "sf", "ff", "fs", "ss", "ll", "hh", "hl", "lh"] + +[Files] +baseDirectory='../analog/test/' +spiceFile='test_low_pvt_source_mc.spice' +outputDirectory='isource' + +[Parameter] +; ID=[-20e-6, -10e-6, 0, 10e-6, 20e-6] +; FB_DIS=[0] +; FB_PP_EN=[0]
diff --git a/scripts/isource_pls.ini b/scripts/isource_pls.ini new file mode 100644 index 0000000..f284bd2 --- /dev/null +++ b/scripts/isource_pls.ini
@@ -0,0 +1,18 @@ +[Setup] +maxCpus=10 +voltages=[1.7, 1.8, 1.9] +corners=["tt", "sf", "ff", "fs", "ss", "ll", "hh", "hl", "lh"] + +[Files] +baseDirectory='../layout/isource/' +spiceFile='test_low_pvt_source_mc.spice' +outputDirectory='pls' + +[Parameter] +; ID=[-20e-6, -10e-6, 0, 10e-6, 20e-6] +; FB_DIS=[0] +; FB_PP_EN=[0] + +[Plot] +OP_OP={'i(V0)':{'ylim_top': 1e-5, 'title':'Current source output', 'y_label':'Output current / A'}, 'vdd':None} +
diff --git a/scripts/optimize.py b/scripts/optimize.py new file mode 100644 index 0000000..a0b86a6 --- /dev/null +++ b/scripts/optimize.py
@@ -0,0 +1,96 @@ +def optimize(): + + file_in='/home/simon/code/asic/analog/tia2/shahdoost_optim1.spice' + workingDir='/home/simon/code/asic/analog/tia2/shahdoost_optim1' + cacheDir='/home/simon/optimCache' + if not os.path.exists(workingDir): + os.mkdir(workingDir) + if not os.path.exists(cacheDir): + os.mkdir(cacheDir) + + ngSpice=NgSpice(file_in, workingDir, parallel=True) + spiceReplace={'vdd#branch':'supply_current'} + ngOpimizer=NgOptimizer(ngSpice, spiceReplace, cacheDir) + #fixedParam={'V_SUPPLY': 1.8, 'M1_L': 2, 'M2_L': 0.3, 'M3_L': 0.3} + fixedParam={'V_SUPPLY': 1.8} #, 'M1_L': 2, 'M2_L': 0.3, 'M3_L': 0.3} + # optimize={ + # 'M1_W':[2, 1, 25],\ + # 'M2_W':[2, 1, 25],\ + # 'M3_W':[2, 1, 25],\ + # 'V_BIAS1': [0.8, 0.5, 1.8],\ + # 'V_BIAS2': [1, 0.5, 1.8],\ + # 'R3_L': [10, 5, 100],\ + # } + # + optimize={ + 'M1_W':[2, 1, 25],\ + 'M2_W':[2, 1, 25],\ + 'M3_W':[2, 1, 25],\ + 'M1_L':[2, 0.15, 6],\ + 'M2_L':[0.3, 0.15, 3],\ + 'M3_L':[0.3, 0.15, 3],\ + 'V_BIAS1': [0.8, 0, 1.2],\ + 'V_BIAS2': [1, 0.6, 1.8],\ + 'R3_L': [10, 5, 100],\ + } + + + ngOpimizer.setFixedParam(fixedParam) + ngOpimizer.setOptimizerParam(optimize) + # Define the cost functions. + ngOpimizer.setCost({'bw': "lambda x: 0 if x > 1E9 else math.log(abs(x-1E9), 10)*100",\ + 'dc_gain': "lambda x: 0 if x > 45 else abs((x-45)*100)",\ + 'supply_current': "lambda x: 0 if abs(x) < 1E-2 else (abs(x)-1E-2)*100",\ + }) + ngOpimizer.optimize() + + + + +def optimize2(): + + file_in='/home/simon/code/asic/analog/tia/tia_lownoise2_optim.spice' + workingDir='/home/simon/code/asic/analog/tia/optim' + cacheDir='/home/simon/optimCache' + if not os.path.exists(workingDir): + os.mkdir(workingDir) + if not os.path.exists(cacheDir): + os.mkdir(cacheDir) + + ngSpice=NgSpice(file_in, workingDir, parallel=True) + spiceReplace={'vdd#branch':'supply_current'} + ngOpimizer=NgOptimizer(ngSpice, spiceReplace, cacheDir) + #fixedParam={'V_SUPPLY': 1.8, 'M1_L': 2, 'M2_L': 0.3, 'M3_L': 0.3} + fixedParam={}#'V_SUPPLY': 1.8} #, 'M1_L': 2, 'M2_L': 0.3, 'M3_L': 0.3} + # optimize={ + # 'M1_W':[2, 1, 25],\ + # 'M2_W':[2, 1, 25],\ + # 'M3_W':[2, 1, 25],\ + # 'V_BIAS1': [0.8, 0.5, 1.8],\ + # 'V_BIAS2': [1, 0.5, 1.8],\ + # 'R3_L': [10, 5, 100],\ + # } + # + optimize={ + 'M8':[2, 0.3, 3],\ + 'M7':[2, 0.3, 3],\ + 'M5':[2, 0.3, 3],\ + 'I3':[50, 30, 100],\ + 'M15':[0.5, 0.3, 3],\ + 'R1':[0.5, 1, 6] + } + + + ngOpimizer.setFixedParam(fixedParam) + ngOpimizer.setOptimizerParam(optimize) + # Define the cost functions. + #gain max_gain min_gain edge_freq + #inoise_total onoise_total + # 'max_gain': "lambda x: 0 if x > 45 else abs((x-45)*100)",\ + # 'min_gain': "lambda x: 0 if abs(x) < 1E-2 else (abs(x)-1E-2)*100",\ + + ngOpimizer.setCost({'gain': "lambda x: 0 if x > 50 else (50-x)*1000",\ + 'edge_freq': "lambda x: 0 if x > 1E9 else math.log(abs(x-1E9), 10)*100",\ + 'inoise_total': "lambda x: 0 if x < 1.6E-7 else (x-1.6E-7)*1E7",\ + }) + ngOpimizer.optimize()
diff --git a/scripts/optimizer.py b/scripts/optimizer.py new file mode 100644 index 0000000..dfbc6b3 --- /dev/null +++ b/scripts/optimizer.py
@@ -0,0 +1,310 @@ +from scipy.optimize import least_squares, differential_evolution +import multiprocessing +import pandas as pd +from matplotlib.mlab import angle_spectrum +import numpy as np +import inspect +import gc +import time +from multiprocessing import reduction +import math +import os +import hashlib +import pickle + +class NgOptimizer(): + ''' + Implements a circuit optimizer + + ''' + def __init__(self, ngSpiceInstance, spiceReplace, cacheDir): + ''' + + :param ngSpiceInstance: + :param spiceReplace: + + ''' + self.fixedParam={} + self.optimizeParam={} + self.spiceReplace=spiceReplace + self.method='diffevol' + self.ngSpiceInstance=ngSpiceInstance + if self.method=='diffevol': + self.ngSpiceInstance.parallel=False + self.cacheDir=cacheDir + + + def setFixedParam(self, param, keepOld=False): + ''' + + ''' + if not keepOld: self.fixedParam={} + + for k, v in param.items(): + self.fixedParam[k] = v + + def setOptimizerParam(self, optimizeParam, keepOld=False): + ''' + + :param initialGuess: {value: [ignored, min, max] } + :tpye initialGuess: dict + ''' + #key: value: list; [0]: ignored [1]: min [2]: max + if not keepOld: self.optimizeParam={} + + for k, v in optimizeParam.items(): + self.optimizeParam[k] = v + + + + def setCost(self, cost): + ''' + ''' + self.cost=cost + + + def optimize(self): + ''' + + ''' + self.ngSpiceInstance.setConstants(self.fixedParam, keepOld=False) + self.spiceFileHash=self.ngSpiceInstance.getFileHash() + self.cacheDir=os.path.join(self.cacheDir, self.spiceFileHash) + + if not os.path.exists(self.cacheDir): + os.mkdir(self.cacheDir) + + # We need a defined sequence of parameters + self.parSequence=list(self.optimizeParam.keys()) + self.costSequence=list(self.cost.keys()) + # Generate a list out of the initial guess dictionary. + initialGuess=[self.optimizeParam[k][0] for k in self.parSequence] + # Generate a dummy result + + # TODO: Perform a test run with spice using the initial guess to verify we get results for all parameters needed for cost calculation. + + + # Get boudaries for parameter space + bounds=[(self.optimizeParam[k][1],self.optimizeParam[k][2]) for k in self.parSequence] + if self.method == 'diffevol': + self.firstRun=True + default=9e10 +# bestResult=differential_evolution(differentialEvolutionFunc, bounds, args=(default), maxiter=1)#, workers=-1)#, bounds, args=default, ) + + bestResult=differential_evolution(self._deFunc, bounds, workers=-1, mutation=(0.1,1.5), seed=1)#, )#, bounds, args=default, ) + print(bestResult) + resultDict={k:v for k,v in zip(self.parSequence, bestResult['x'])} + print('Optimization result: ', resultDict) + + elif self.method == 'leastsq': + default=[9e10 for k in self.cost.keys()] + + # Iterate + for iter in range(10): + simulationResult={} + for iteration in range(20): + maxfev=(iteration+1)*multiprocessing.cpu_count()*2 + + print("maxfev: ", maxfev) + bestResult=least_squares(leastsqFunc, initialGuess, args=(simulationResult, default), max_nfev=maxfev, method='dogbox') + print('Iteration: ', iteration) + print("Best result: ", bestResult['x']) + print("Cost: ", bestResult['fun']) + spiceResult=self._runSpiceMulti(simulationResult) + simulationResult=self._calcCost(simulationResult, spiceResult) + #print(simulationResult) + + initialGuess=bestResult['x'] + gc.collect() + + #elif self.method == 'leastsq' + + + def _deFunc(self, param): + ''' + + ''' + + # We use a cache to speed up multiple runs. + paramHash=hashlib.sha1(param.tobytes()).hexdigest() + + cacheFile=os.path.join(self.cacheDir, paramHash) + + + if os.path.exists(cacheFile): + # If we have a cache hit simply load the result from the file. + with open(cacheFile, 'rb') as file: + result, cost=pickle.load(file) + else: + spiceResult=self._runSpiceSingle(param) + result=self._spiceResultToDict(spiceResult, True) + if len(result) != 1: + raise(RuntimeError(f'This is a bug. Expected 1 simulation result. Got {len(result)}')) + cost=self._calcCostSingle(result[0]) + with open(cacheFile, 'wb') as file: + pickle.dump((result, cost), file) + sumCost=np.sum(cost) + print('\nResult: ', result[0], ' Cost: ', cost, ' Sum Cost: ', sumCost) + return sumCost + + def _runSpiceSingle(self, param): + forSpice=[] + # Generate a dataframe we can provide to out NgSpice class as input. + # Combine the parameter names from parSequence with the values requested by least_squares. + forSpice.append({k:p for k,p in zip(self.parSequence, param)}) + # Transform the dict to a dataframe + df=pd.DataFrame.from_dict(forSpice) + # Run Ngspice + spiceResult=self.ngSpiceInstance.run(df, delete=True, rename=self.spiceReplace) + + return spiceResult + + def _runSpiceMulti(self, simulationResult): + ''' + Run the spice simulation + ''' + + forSpice=[] + # Generate a dataframe we can provide to out NgSpice class as input. + for par, simResult in simulationResult.items(): + # If for given parameters we don't have a simulation result create an input for Spice + if simResult[1] is None: + # Combine the parameter names from parSequence with the values requested by least_squares. + forSpice.append({k:p for k,p in zip(self.parSequence, simResult[0])}) + + # Transform the dict to a dataframe + df=pd.DataFrame.from_dict(forSpice) + + # Run Ngspice + spiceResult=self.ngSpiceInstance.run(df, delete=True, rename=self.spiceReplace) + return spiceResult + + def _spiceResultToDict(self, spiceResult, ignoreMissingResult): + ''' + Converts the results of a series of spice simulations to dictionaries. + + :param spiceResult: Result of :py:meth:ǸgSpice.run. + + :returns: A list of dictionaries: The key in the dictionary is part of self.costSequence. The value corresponds to the output of spice. + ''' + # Our simulation result should match our cost functions. + # Flatten the result and convert the pandas data frames to a dictionary. + # The column names of interest are the key of the initial guess. + columnNames=self.cost.keys() + + result=[] + + for spiceR in spiceResult: + + resultDict={} + # Fill the dictionary. + for column in columnNames: + for res in spiceR['results']: + if column in res.columns: + # Only consider unique results. + resultDict[column] = res[column].unique() + + # Do sanity checks. + for column in columnNames: + + # First of all we want a result for each parameter for which a cost was defined. + if not column in resultDict: + # Handle the situation in which we don't have a result. + if ignoreMissingResult: + resultDict[column] = np.NaN + else: + raise(RuntimeError(f'No spice simulation result for item {column}')) + self.firstRun=False + # Try to convert numpy arrays to float. + if type(resultDict[column]) == np.ndarray: + if len(resultDict[column]) == 1: + resultDict[column]=resultDict[column][0] + # If the have a result it has to be unique. + datatype= type(resultDict[column]) + if not datatype == float and not datatype == np.float64: + raise(RuntimeError(f"Got data type {datatype}. I'm interpreting this as non-unique simulation result")) + # Add the dictionary to the result + result.append(resultDict) + + return result + + def _calcCost(self, simulationResult, spiceResult): + + # Fill simulationResult with the results of the simulation. + for par, simResult in simulationResult.items(): + # We only need to add a result if none is there. + if simResult[1] is None: + # Reconstruct the spice input parameters + forSpice={k:p for k,p in zip(self.parSequence, simResult[0])} + # Search the simulation result for the given simulation set of input parameters. + for s in spiceResult: + if forSpice == s['param']: + # Apply cost funtion + #result=s['results'] + resultDict=self._spiceResultToDict(s, True) + + costResult=self._calcCostSingle(resultDict) + + simulationResult[par] = (simResult[0], costResult) + # Do some sanity checks. E.g. if we don't have a result, we should fail. + if simulationResult[par][1] is None: + raise(RuntimeError('Failed to find simulation results for a given set of parameters. This is a Bug.')) + return simulationResult + + def _calcCostSingle(self, result): + ''' + Apply the cost function to a single simulation result. + ''' + costResult=[] + + for costName in self.costSequence: + # Get the numeric result + resultValue=result[costName] + # Get the cost function + costFunction=eval(self.cost[costName]) + # Apply the const function + # If we have a NaN reutrn a very large cost + if np.isnan(resultValue): + costResult=[1E9 for x in self.costSequence] + #if sumCost: + return costResult + else: + try: + cost=costFunction(resultValue) + except: + print("Cost calculation failed.") + print(f"Spice simulation result: {resultValue}") + print(f"Cost name: {costName}") + print(f"Cost function: ", inspect.getsource(costFunction)) + raise + costResult.append(cost) + # Write the result of the cost calculation to the simulation result. +# if sumCost: + # costResult=np.sum(costResult) + + return costResult + +def differentialEvolutionFunc(a, b): + ''' + + ''' + print("Function call") + + +def leastsqFunc(param, result, default): + ''' + Funtion for least_squares. We don't run spice in here. Instead we collect input for spice and return know spice parameters. + ''' + + hash=str(param) + + if hash in result: + res= result[hash][1] + else: + result[hash] = (param, None) + res=default + print(param,res) + if res is None: + res = default + return res +
diff --git a/scripts/spice.py b/scripts/spice.py new file mode 100644 index 0000000..4ed1b8b --- /dev/null +++ b/scripts/spice.py
@@ -0,0 +1,634 @@ +import os +import subprocess +from subprocess import PIPE +from multiprocessing import Pool, cpu_count +import shutil +import re +import pandas as pd +from multiprocessing import Lock +import hashlib +import math +import xarray as xr +import numpy as np +import time +import pickle +class SpiceAnalysis(): + def __init__(self): + pass + + +class RawFile(): + ''' + Read the given ngspice .raw file + ''' + def __init__(self, file): + pass + +class NgSpiceFile(): + ''' + Reading and modifying an ngspice file + + ''' + def __init__(self, file): + self.spice=[] + self.file=file + self.paramPairs={} + self.commandBlPairs={} + with open(self.file, 'r') as myfile: + for line in myfile: + self.spice.append(line) + + def findReplaceable(self): + ''' + Searches the file for replaceable parameters. + Replaceable parameters are characterized by a name enclosed in # without spaces. E.g. #Name# + Note: The name #OP# is reserved. It must not be used and will be ignored. + + ''' + result=[] + for line in self.spice: + candidate=line.split('#') + for i, c in enumerate(candidate): + # The first candidate in a line is not valid + if i == 0: + continue + # OP is not a valid candidate + if c == 'OP': + continue + if not ' ' in c and not '\n' in c: + result.append(c) + return result + + def opExtraction(self, directory): + ''' + Replaces the keyword *#OP# with a block saving information about operating point of all transistors including capacitances. + Make sure the have an OP statement before the keyword *#OP#. Also, *#OP# must be part of a control block. + + :param directory: Directory into which the operating point information should be written. + ''' + + EXTRACT_PARAM=["vds", "gm", "vth", "vgs", "id", "gds", "cgg", "cgs", "cgd", "cgb", "cdg", "cds", "cdd", "cdb", "csg", "css", "csd", "csb", "cbg", "cbs", "cbd", "cbb"] + + transistors = self._getTransistorModelNames() + + # Generate the string for + + string = "" + for name, model in transistors.items(): + outfile=os.path.join(directory, f"op_{name}.csv") + subString="wrdata {} ".format(str(outfile)) + for param in EXTRACT_PARAM: + subString+="@m.{}.m{}[{}] ".format(name.lower(), model, param) + + string += f"{subString}\n" + + self.commandBlPairs["*#OP#"] = string + + def _getTransistorModelNames(self): + ''' + Returns a list of the model names of transistors. Note: This only works for SK130A for now as we search for sky130 in the model name. + + ''' + result={} + for line in self.spice: + candidate=line.split(' ') + # Check if the name contains XM which stands for transistor + #print(candidate[0][:2]) + if candidate[0][:2] == 'XM': + for c in candidate: + #print(c[:6]) + # Model names contain "sky130" at the beginning. Find the model name. + if c[:6] == "sky130": + result[candidate[0]] = c + + return result + + def replace(self, pairs, keepOld=False): + ''' + Replace the replaceable parameters + + :param pairs: Dictionary, They key is the name of the replaceable parameters. The value the value to be written to the output file. + :type pairs: dict + ''' + + replaceable=self.findReplaceable() + # + if not keepOld: self.paramPairs={} + + for k, v in pairs.items(): + if not k in replaceable: + raise(RuntimeError(f'Cannot replace {k}. {k} not found in spice file')) + self.paramPairs[k] = v + + def write(self, file): + ''' + Writes out the modified spice file. The file is written to the rundir given when instatiating the class. + + :param + + ''' + + # Check if we have parameters for all replaceable strings in the spice file. + replaceable=self.findReplaceable() + for r in replaceable: + if not r in self.paramPairs.keys(): + raise(RuntimeError(f'No value found for {r}. Please provide values for all replaceable strings.')) + + self.outfile=file + #outfile=os.path.join(self.rundir, file) + + outspice=[] + for line in self.spice: + # Replace the parameters + for k,v in self.paramPairs.items(): + line=line.replace('#'+k+'#', str(v)) + + # Replace command blocks + for k,v in self.commandBlPairs.items(): + line=line.replace(k, v) + + outspice.append(line) + + # Write to file. + with open(self.outfile, 'w') as outfile: + for line in outspice: + outfile.write(line) + +def splitNgspiceFileName(fileName): + ''' + Splits the file name returned by ngspice into it's parts. + + Please follow the following naming scheme: + result_type_name_temperature_mcrunno.csv + In detail: + + - "result": Constant. Must be present for :py:class:`Ngspice` to recognize the file. + - "type": Currently allowed are "AC", "NOISE", "MEAS". + - "name": You might want to have multiple simulations of the same type. You can distinguish them by name. + - "temperature": The temperature followed by deg. E.g. 80deg. + - "mcrunno": Number of the monte carlo run. + + :returns: type, name, temperature, mcrunno + ''' + try: + # Remove extension + fileName=os.path.splitext(fileName)[0] + fileName_split=fileName.split('_') + simu_type=fileName_split[1] + simu_name=fileName_split[2] + simu_temp=re.findall('[0-9]*deg', fileName_split[3]) + simu_temp=re.findall('[0-9]*', simu_temp[0]) + simu_temp=int(simu_temp[0]) + #mcrunno=fileName_split[4].split('.') + mcrunno=int(fileName_split[4]) + except: + print('Failed to parse file name {}'.format(fileName)) + raise + + return simu_type, simu_name, simu_temp, mcrunno + + +class NgSpice(): + ''' + Enables investigation of a parameter space using parallel processes of ngspice. + + Concept and usage: + * In the spice file mark parameters to be altered with #Name#. + * In the spice file mark the position at which the operating point should be saved with "*#OP#". + * In the spice file write results to .csv files. The filename has to comply with the convetions defined in :py:func:`splitNgspiceFileName` + * Instantiate this class for the given spice file. You can then call :py:meth:`setParameterSpace` to replace the parameters to be altered. + + ''' + def __init__(self, file, rundir, parallel=True, max_cpus=24): + ''' + :param rundir: Directory used for temporary storage. + :param file: Spice file. + :param parallel: Optional. Defaults to True. Set to false to disable processing on multiple cpus. + :param max_cpus: Maximum number of CPUs to be used. Defaults to 24. + + ''' + self.rundir=rundir + self.file=file + #self.includeFiles=includeFiles + self.constantPairs={} + + self.ngspiceFile = file + self.parallel=parallel + self.folderOffset=0 + self.simulation_result=None + self.max_cpus=max_cpus + # This is obsolete. The function is coverd by spiceParam. + # def setConstants(self, pairs, keepOld): + # ''' + # Sets the parameters to be replaced by constants. + # + # :param pairs: Dictionary, They key is the name of the replaceable parameters. The value the value to be written to the output file. + # :type pairs: dict + # ''' + # if not keepOld: self.pairs={} + # + # for k, v in pairs.items(): + # self.constantPairs[k] = v + # + + def getFileHash(self): + ''' + Returns a hash for the spice file. + + ''' + hash=hashlib.sha1() + + with open(self.ngspiceFile) as file: + for line in file: + hash.update(line.encode('utf-8')) + + return hash.hexdigest() + + def getResultAsDataArray(self): + ''' + Groups the results from :py:meth:`Ngspice.run` by simulation type and converts the results + to a big dataframe. + + The function :py:meth:`Ngspice.run` returns a list of simulation results. + The results may stem from AC analysis, OP calculation, measurements, etc. + The results are also not organized in a very handy form. + + This function will group the results by measurement type. Since there is no easy way to infer + the type of simulation from the results you must provide the type of data via the file + name generated by ngspice. + + :returns: {"type": {"name": xarray}} + + ''' + # Our simulation result is multidimensional. We use xarrays to store the data. + # Uppon creation of the array we need to know the exact size of the array. + # Some dimensions are known from spiceParam. Others, in particular temperature need to be + # inferred from the simulation results. Therefore we iterate over our simulation results twice. + # Firstly we extract the parameters given to the simulation from within ngspice. + # Secondly we initialize fill the xarray, then we fill the xarray. + + if self.simulation_result is None: + raise(RuntimeError('You must run a simulation first')) + + simulationResult=self.simulation_result + spiceParam=self.spiceParam + # Fill params from simulation results + param={} + t=time.time() + simulationResultNext=[] + for res in simulationResult: + # Iterate over the results of a simulation run. + keepResult=True + for fileName, simu_result in zip(res['resultFiles'], res['results']) : + # Get the type and name of the simulation + simu_type, simu_name, simu_temp, mcrunno=splitNgspiceFileName(fileName) + # Check if the type has already been added if not initialize + # TODO: Consider doing some sanity check on simulation types. + # We might want to reduce the amount of data stored for measurements. + + if not simu_type in param: + param[simu_type]={} + + # Check if the name has already been added if not initialize + if not simu_name in param[simu_type]: + param[simu_type][simu_name]={'temp':[], 'mcrunno':[]} + # Add frequencies + columns=list(simu_result.columns) + if 'frequency' in columns: + param[simu_type][simu_name]['frequency']=simu_result['frequency'].values + columns.remove('frequency') + # Same with time + if 'time' in columns: + param[simu_type][simu_name]['time']=simu_result['time'].values + columns.remove('time') + + + # Add spice vectors + param[simu_type][simu_name]['spice_vector']=columns + + # See if the temperature is already listed + if not simu_temp in param[simu_type][simu_name]['temp']: + param[simu_type][simu_name]['temp'].append(simu_temp) + + # See if the mcrunno is already listed- + # Note: Some run numbers might be skipped sometimes. + # This way we get all unique numbers. + if not mcrunno in param[simu_type][simu_name]['mcrunno']: + param[simu_type][simu_name]['mcrunno'].append(mcrunno) + + # Ensure frequencies match. + if 'frequency' in simu_result.columns: + freq_saved= param[simu_type][simu_name]['frequency'] + freq_this_result = simu_result['frequency'].values + if not np.array_equal(freq_saved,freq_this_result): + raise(RuntimeError("Frequencies don't match. Something went wrong.")) + + # Remove additional points from time axis for transient simulation + if 'time' in simu_result.columns: + time_saved= param[simu_type][simu_name]['time'] + time_this_result = simu_result['time'].values + if not np.array_equal(time_saved,time_this_result): + time_intersect=np.msort(np.union1d(time_saved, time_this_result)) + param[simu_type][simu_name]['time']=time_intersect + + print('Assemble parameter space:', time.time()-t ) + + # + if keepResult: + simulationResultNext.append(res) + # Fill params from spiceParam and + # compute the dimensions from params + for simu_type in param.keys(): + for simu_name in param[simu_type].keys(): + # Fill params from spiceParam + for key, value in spiceParam.items(): + # Convert floats or integers to lists, as required by xarray. + # TODO: We might need to convert more data types. + if type(value) == float or type(value) == int: + value=[value] + param[simu_type][simu_name][key]=value + + # Iterate over simulation runs and fill the xarray. + # One simulation run is one invocation of ngspice. + result={} + t=time.time() + t_fill=0 + for res in simulationResultNext: + # Iterate over the results of a simulation run. + simu_param=res['param'] + for fileName, simu_result in zip(res['resultFiles'],res['results']) : + # Get the type and name of the simulation + simu_type, simu_name, simu_temp, mcrunno=splitNgspiceFileName(fileName) + + if not simu_type in result: + result[simu_type]={} + + # Check if we already encountered another run of the simulation. + # If this is the first time we come across the simulation, initialize the + # xarray. + if not simu_name in result[simu_type]: + coords=param[simu_type][simu_name] + dims=param[simu_type][simu_name].keys() + result[simu_type][simu_name]=xr.DataArray(None, coords=coords, dims=dims) + + # Assemble our current coordinates :) + # Frequency and spice vectors are treated separately. + t1=time.time() + coord=simu_param + coord['temp']=simu_temp + coord['mcrunno']=mcrunno + columns=list(simu_result.columns) + # Attach the frequency to the coordinates + #if simu_type=='op': + # print('Breakpoint') + if 'frequency' in columns: + columns.remove('frequency') + frequency=simu_result['frequency'].values + coord['frequency']=frequency + else: + if 'frequency' in coord: + coord.pop('frequency') + + # Handle time in transient simulations + if 'time' in columns: + columns.remove('time') + time_param=coords['time'] + time_simu=simu_result['time'] + else: + time_param=None + + # Attach the data to the array. + for column in columns: + coord['spice_vector']=column + values=simu_result[column].values + if len(values) == 1: + values=values[0] + try: + if time_param is None: + result[simu_type][simu_name].loc[coord]=values + elif np.array_equal(time_param, time_simu): + result[simu_type][simu_name].loc[coord]=values + else: + i=0 + val=np.empty_like(time_param) + for j, tp in enumerate(time_param): + ts= time_simu[i] + if tp == ts: + val[j]=values[i] + i+=1 + else: + val[j]=np.nan + + result[simu_type][simu_name].loc[coord]=val + except: + print(simu_type, simu_name, coord) + raise + t_fill+=time.time()-t1 + + print('Fill parameter space:', time.time()-t ) + print('xrtime:', t_fill) + return result + + def savePickle(self, fileName): + ''' + Stores the simulation result and the spiceParameters in a pickle file. + ''' + d_out={'simulationResult': self.simulation_result, 'spiceParam':self.spiceParam} + with open(fileName, 'wb') as file: + pickle.dump(d_out, file) + + def loadPickle(self, fileName): + ''' + Loads the simulation result and the spiceParameters from a pickle file. + ''' + with open(fileName, 'rb') as file: + d_out=pickle.load(file) + + self.simulation_result=d_out['simulationResult'] + self.spiceParam=d_out['spiceParam'] + + def _spiceParamToParSpace(self, spiceParam, parSpace, spiceParamKeyList, indexParam): + ''' + Iteratively create the parameter space for ngspice + ''' + parSpaceNew={k: [] for k in parSpace.keys()} + new_key=spiceParamKeyList[indexParam] + parSpaceNew[new_key]=[] + for new_value in spiceParam[new_key]: + # Handle the first iteration + if parSpace =={}: + parSpaceNew[new_key].append(new_value) + continue + # All further iteration + # Copy existing parameters + for key, value in parSpace.items(): + length=len(value) + for v in value: + parSpaceNew[key].append(v) + # Add new parameter + for v in range(length): + parSpaceNew[new_key].append(new_value) + + if indexParam >= len(spiceParamKeyList)-1: + return parSpaceNew + else: + parSpaceNew=self._spiceParamToParSpace(spiceParam, parSpaceNew, spiceParamKeyList, indexParam+1) + return parSpaceNew + + def spiceParamToParSpace(self, spiceParam): + ''' + Create the parameter space for ngspice + + ''' + parSpaceDict={} + spiceParamKeyList=list(spiceParam.keys()) + parSpaceDict=self._spiceParamToParSpace(spiceParam, parSpaceDict, spiceParamKeyList, 0) + parSpace=pd.DataFrame(parSpaceDict) + return parSpace + + def run(self, spiceParam, delete=False, rename={}): + ''' + Executes ngspice. + + Operations performed: + 1. In the rundir provided to __init__ a directory will be created for each row in parSpace. + 2. In the created directories, a spice file run.spice will be created for each row in parSpace. + 3. ngspice will be called executed. The terminal output will be stored in run.log + 4. Files created by ngspice called result{}.csv (where {} is arbitrary) will be parsed as csv files and content returned. + + + :param spiceParam: Dictionary. Keys are the parameters to be varies. Values are lists containing the values. + :param delete: + :param replace: Optional. Dict. If given, the column names given by key in the output will be replaced by the corresponding value. This should help in making the output human readable. + :param threadSafe: Will not overWrite existing results + + :returns: List. Each entry corresponds to ngspice run. + Each entry is a dict containing the variable parameters used for the call as well as the contents of the read result files. + ''' + # Convert everything to lists + # TODO: Add more data types + self.spiceParam={} + for k, v in spiceParam.items(): + if type(v) == float: + v=[v] + self.spiceParam[k]=v + + # Create the parameter space for ngspice. + self.parSpace=self.spiceParamToParSpace(self.spiceParam) + parSpace=self.parSpace + + # Generate parameter map. + + # Create run directories + noInstances=parSpace.shape[0] + print('Number of spice Instances to be started: ', noInstances) + rundirs=[] + + # This needs to be thread safe so we can have multiple calls of run in parallel. + for s in range(noInstances): + # Assemble the directory name for the run. + hash=hashlib.sha1() + for col in parSpace.columns: + # If we have a string encode it as utf 8 before calculating a hash. + # For other data types attempt to use the tobyte functions. + # Note: This might fail for some data types. + val=parSpace[col].values[s] + if type(val) == str: + hash.update(val.encode('utf-8')) + else: + hash.update(val.tobytes()) + + dirName=hash.hexdigest() + rundir=os.path.join(self.rundir, f'rundir_{dirName}') + if os.path.exists(rundir): + # If a run directory exists, delete it or give up. + if delete: + shutil.rmtree(rundir) + else: + raise(RuntimeError('Rundir exists.')) + # Create the directory. + os.mkdir(rundir) + rundirs.append(rundir) + + # We instantiate NgSpiceFile at each call to be thread safe. + + ngspiceFile=NgSpiceFile(self.ngspiceFile) + ngspiceFile.replace(self.constantPairs, keepOld=False) + + # Write spice files + spiceFiles=[] + paramForRes=[] + for (_, item), rundir in zip(parSpace.iterrows(), rundirs): + # Set the new parameter + param=item.to_dict() + ngspiceFile.replace(param, keepOld=True) + paramForRes.append(param) + + # Write the spice file to the output directory. + # Spice will be run in rundir, so we need to use absolute pathes. + spiceFile=os.path.abspath(os.path.join(rundir, 'run.spice')) + spiceFiles.append(spiceFile) + ngspiceFile.write(spiceFile) + + # A bit of cryptic code :) + # Pool only allows one parameter to be passed to the function. So we compress the parameters to a dict. + param=[ {'runDir':runDir, 'spiceFile':spiceFile, 'param': pForRes} for (runDir, spiceFile, pForRes) in zip(rundirs, spiceFiles, paramForRes)] + + # Ngspice also seems to do some multithreading. So let's split the task into batches of equal number of processes. + # E.g. if we have 9 spice instances we need to run don't start 8 at once and then 1 but run up to 5 in parallel, resulting in approx. 5+4 instances. + cpus= min(cpu_count(), self.max_cpus) + if cpus != cpu_count(): + print(f'Limiting the maximum number of processes to {cpus}. However, {cpu_count()} CPUs would be available. \n \ + Change the max_cpus if you would like to use more CPUs.') + batches= math.ceil(noInstances/cpus) + processes=int(math.ceil(noInstances/batches)) + + print(f'Will need {batches} batch(es). Max processes per batch: {processes}.') + + # Execute ngspice + if self.parallel: + p=Pool(processes=processes) + with p as pool: + result=pool.map(_runNgspice, param) + del p + else: + result=[] + for p in param: + result.append(_runNgspice(p)) + + # Rename columns to something more easily understandable than spice internal variable names + for res in result: + for df in res['results']: + df.rename(columns=rename, inplace=True) + + self.simulation_result=result + return result + +def _runNgspice(param): + ''' + Runs ngspice, to be called as part of a multiprocessing pool + ''' + + # Execute ngspice in a shell, so the user can see what's happening. Also output the stdout to a file + runDir=param['runDir'] + spiceFile=param['spiceFile'] + logfile= os.path.join(runDir, 'run.log') + with open(logfile, 'wb') as nglog: + with subprocess.Popen(['ngspice', '-b', f'{spiceFile}'], shell=False, cwd=runDir, stdout=PIPE) as proc: + nglog.write(proc.stdout.read()) + + #Search for result files + regex=r'result.*\.csv$' + result={'param': param['param'], 'resultFiles': [], 'results': [] } + for root, _, files in os.walk(runDir): + for file in files: + m = re.match(regex, file) + if not m is None: + df=pd.read_csv(os.path.join(root, file), delim_whitespace=True) + #print(df) + result['resultFiles'].append(file) + result['results'].append(df) + return result + + + \ No newline at end of file
diff --git a/scripts/tia_fb_en.ini b/scripts/tia_fb_en.ini new file mode 100644 index 0000000..dcf81c1 --- /dev/null +++ b/scripts/tia_fb_en.ini
@@ -0,0 +1,17 @@ +[Setup] +maxCpus=10 +voltages=[1.7, 1.8, 1.9] +corners=["tt", "sf", "ff", "fs", "ss", "ll", "hh", "hl", "lh"] + +[Files] +baseDirectory='../analog/test/' +spiceFile='test_tia_rgc_full_mc.spice' +outputDirectory='fb_on' + +[Parameter] +ID=[-20e-6, -10e-6, 0, 10e-6, 20e-6] +FB_DIS=[0] +FB_PP_EN=[0] + +[Plot] +ylim_top=None \ No newline at end of file
diff --git a/scripts/tia_fb_pp_en.ini b/scripts/tia_fb_pp_en.ini new file mode 100644 index 0000000..7d2da59 --- /dev/null +++ b/scripts/tia_fb_pp_en.ini
@@ -0,0 +1,14 @@ +[Setup] +maxCpus=10 +voltages=[1.7, 1.8, 1.9] +corners=["tt", "sf", "ff", "fs", "ss", "ll", "hh", "hl", "lh"] + +[Files] +baseDirectory='../analog/test/' +spiceFile='test_tia_rgc_full_mc.spice' +outputDirectory='fb_pp' + +[Parameter] +ID=[-200e-6, -10e-6, 0, 10e-6, 200e-6] +FB_DIS=[0] +FB_PP_EN=[1.8]
diff --git a/scripts/tia_integration.ini b/scripts/tia_integration.ini new file mode 100644 index 0000000..bd52bbf --- /dev/null +++ b/scripts/tia_integration.ini
@@ -0,0 +1,24 @@ +[Setup] +maxCpus=10 +voltages=[1.7, 1.8, 1.9] +corners=["tt", "sf", "ff", "fs", "ss", "ll", "hh", "hl", "lh"] + +[Files] +baseDirectory='../analog/test/' +spiceFile='test_tia_rgc_integration_mc.spice' +outputDirectory='integration' + +[Parameter] +; ID=[-20e-6, -10e-6, 0, 10e-6, 20e-6] +; FB_DIS=[0] +; FB_PP_EN=[0] + +[Plot] +noise_outov=None +noise_outzoom=None +noise_tiaov=None +noise_tiazoom=None +ac_ac={'db(v(v_outp)-v(v_outn))': {'ylim_bottom': -20, 'title':'Output gain', 'y_label':'Gain / dBOhm'}, 'vdb(vtia_outp_1)': {'ylim_bottom': -20, 'title':'Tia gain', 'y_label':'Gain / dBOhm'}, 'v(vinb)': {'title':'Input impedance', 'y_label':'Impedance / Ohm'}, 'v(vinb).1':None} +meas_out=None +meas_tia=None +op_op=None \ No newline at end of file
diff --git a/scripts/tia_nofb.ini b/scripts/tia_nofb.ini new file mode 100644 index 0000000..711157e --- /dev/null +++ b/scripts/tia_nofb.ini
@@ -0,0 +1,14 @@ +[Setup] +maxCpus=10 +voltages=[1.7, 1.8, 1.9] +corners=["tt", "sf", "ff", "fs", "ss", "ll", "hh", "hl", "lh"] + +[Files] +baseDirectory='../analog/test/' +spiceFile='test_tia_rgc_full_mc.spice' +outputDirectory='fb_off' + +[Parameter] +ID=[-2e-6, -1e-6, 0, 1e-6, 2e-6] +FB_DIS=[1.8] +FB_PP_EN=[0]
diff --git a/scripts/tool_box.py b/scripts/tool_box.py new file mode 100644 index 0000000..5d372f1 --- /dev/null +++ b/scripts/tool_box.py
@@ -0,0 +1,132 @@ +import sys +import os +from multiprocessing import Pool, cpu_count, Process +import json + +def _except_hook(cls, exception, traceback): + ''' + Function used by :py:func:`pqt5_exception_workaround`. Don't call this directly. + ''' + sys.__excepthook__(cls, exception, traceback) + + + +def pqt5_exception_workaround(): + ''' + Workaround to prevent pyqt5 from silently eating exceptions. + Call this at the beginning of your progame and you will experience relieve. + ''' + sys.excepthook = _except_hook + + +class Persival(): + ''' + Keeps values persistent between program runs. + + ''' + + def __init__(self, persival_file='persival.json'): + ''' + Will try to load parameters from the persival_file. + If the file does not exists this will be silently ignored and the file will be created when :py:meth:`save` will be called. + + ''' + + self.persival_file=persival_file + if os.path.exists(persival_file): + with open(self.persival_file, 'r') as file: + self.data=json.load(file) + else: + self.data={} + + def setDefault(self, name, value): + ''' + Sets the given value only if no information was present in the persival file or if the persival file was not found. + ''' + if not name in self.data.keys(): + self.data[name]=value + + def get(self, name): + ''' + Returns the saved value for the given parameter. + ''' + return self.data[name] + + + def set(self, name, value): + ''' + Store the given value in memory. You have to call save to dump the value to the disk. + ''' + self.data[name] = value + # print('Break') + + + def save(self): + ''' + Saves the content to a file. + ''' + with open(self.persival_file, 'w') as file: + json.dump(self.data, file) + + + + +class ProcessingHelper(): + ''' + Helper working around the limitations of Pool. + + ''' + def __init__(self, parallel=False, maxCpu=None): + self.funcs=[] + self.parallel=parallel + self.maxCpu=maxCpu + self.retval=[] + + def add(self, function, args, kwargs={}): + ''' + Add a function to the processing pool. + + :param function: function to be executed + :param args: list. Provice the positional arguments + :param kwargs: dict. Optional, provide the kwargs + ''' + if self.parallel: + self.funcs.append({'func': function, 'args': args, 'kwargs': kwargs}) + else: + retval=function(*args, **kwargs) + self.retval.append(retval) + + def run(self): + ''' + Start processing. + + :returns: list of results. The orgdering matches the sequence of add calls. + ''' + processes=cpu_count() + if not self.maxCpu is None: + processes=min(self.maxCpu, processes) + # Windows does not handle full cpu loads well, so leave one CPU unutilized for Windows. + if os.name == 'nt': + processes -=1 + + if self.parallel: + with Pool(processes=processes) as pool: + result=pool.map(self._myrun, range(len(self.funcs))) + else: + result=self.retval + return result + + + def _myrun(self, paramNo): + ''' + wrapper around the function for pool + + :param paramNo: + ''' + param=self.funcs[paramNo] + func=param['func'] + args= param['args'] + kwargs= param['kwargs'] + retval=func(*args, **kwargs) + return retval + \ No newline at end of file
diff --git a/scripts/transistors.py b/scripts/transistors.py new file mode 100644 index 0000000..282b201 --- /dev/null +++ b/scripts/transistors.py
@@ -0,0 +1,173 @@ +import os +import pandas as pd +from spice import NgSpice + +def plotGmIdCurves(transistor_type, vsb): + from matplotlib import pyplot as plt + + #TRANSISTOR_TYPES=['nfet_01v8_lvt', 'pfet_01v8_lvt', 'pfet_01v8_hvt', 'nfet_01v8', 'pfet_01v8'] + #TRANSISTOR_TYPE='pfet_01v8' + #TRANSISTOR_TYPE='nfet_01v8' + TRANSISTOR_TYPE=transistor_type + + + #file_in='/home/simon/code/asic/analog/transistors/test_nmos4.spice' + #workingDir='/home/simon/code/asic/analog/transistors/outdata_nmos_lvt_US_0_6V' + #workingDir='/home/simon/code/asic/analog/transistors/outdata_nmos_lvt' + workingDir='/home/simon/code/asic/analog/transistors/' + workingDir=os.path.join(workingDir, 'vsb_{}'.format(vsb)) + if TRANSISTOR_TYPE=='pfet_01v8': + file_in='/home/simon/code/asic/analog/transistors/test_pmos.spice' + workingDirT='outdata_pmos' + l=[0.15, 0.2, 0.3, 0.5, 1, 2, 4, 6] + if TRANSISTOR_TYPE=='pfet_01v8_lvt': + file_in='/home/simon/code/asic/analog/transistors/test_pmos_lvt.spice' + workingDirT='outdata_pmos_lvt' + l=[0.35, 0.5, 1, 2, 4, 6] + if TRANSISTOR_TYPE=='pfet_01v8_hvt': + file_in='/home/simon/code/asic/analog/transistors/test_pmos_hvt.spice' + workingDirT='outdata_pmos_hvt' + l=[0.15, 0.2, 0.3, 0.5, 1, 2, 4, 6] + if TRANSISTOR_TYPE=='nfet_01v8': + file_in='/home/simon/code/asic/analog/transistors/test_nmos.spice' + workingDirT='outdata_nmos' + l=[0.15, 0.2, 0.3, 0.5, 1, 2, 4, 6] + if TRANSISTOR_TYPE=='nfet_01v8_lvt': + file_in='/home/simon/code/asic/analog/transistors/test_nmos_lvt.spice' + workingDirT='outdata_nmos_lvt' + l=[0.15, 0.2, 0.3, 0.5, 1, 2, 4, 6] + vsb=[vsb]*len(l) + width={'L': l, 'VSB': vsb} + + if not os.path.exists(workingDir): + os.mkdir(workingDir) + workingDir=os.path.join(workingDir, workingDirT) + + if not os.path.exists(workingDir): + os.mkdir(workingDir) + + #workingDir='/home/simon/code/asic/analog/transistors/outdata_nmos' + + # Generate parameter map. + df=pd.DataFrame(width) + + # Replace the spice specific names by something human readable + # nfet + #spiceReplace={'var_vgs': 'V_GS', 'var_vdb': 'V_DS', 'i(V_ID)' : 'I_D', '@m.xm1.msky130_fd_pr__nfet_01v8[gm]': 'gm', '@m.xm1.msky130_fd_pr__nfet_01v8[gds]': 'gds'} + # nfet_lvt + #spiceReplace={'var_vgb': 'V_GB', 'var_vds': 'V_DS', 'i(V_ID)' : 'I_D', '@m.xm1.msky130_fd_pr__nfet_01v8_lvt[gm]': 'gm', '@m.xm1.msky130_fd_pr__nfet_01v8_lvt[gds]': 'gds'} + + spiceReplace={'var_vgb': 'V_GB', 'var_vds': 'V_DS', 'i(V_ID)' : 'I_D', f'@m.xm1.msky130_fd_pr__{TRANSISTOR_TYPE}[gm]': 'gm', f'@m.xm1.msky130_fd_pr__{TRANSISTOR_TYPE}[gds]': 'gds'} + + # Run spice + ngspice=NgSpice(file_in, workingDir, parallel=True) + try: + result=ngspice.run(df, delete=True, rename=spiceReplace) + except: + print(TRANSISTOR_TYPE) + raise + + + for r in result: + df=r['results'][0] + u_GS_unique=df['V_GB'].unique() + + # First plot: For a given transistor length plot gm as a function of the Gate voltage. + fig, ax = plt.subplots() + for u_GS_filter in u_GS_unique: + myfilter = df['V_GB'].values == u_GS_filter + I_D=df['I_D'].values[myfilter] + gm=df['gm'].values[myfilter] + ax.plot(I_D, gm, label=f'U_GB={u_GS_filter}') + ax.set_xlabel('I_D') # Add an x-label to the axes. + ax.set_ylabel('gm') # Add a y-label to the axes. + ax.set_title("Lenght={:.2f}".format(r['param']['L'])) # Add a title to the axes. + ax.legend() # Add a legend. + #plt.show() + file=os.path.join(workingDir, "gm vs ID, Lenght={:.2f}.png".format(r['param']['L'])) + plt.savefig(file) + plt.close(fig) + + # Second plot: For a given transistor length plot gds as a function of the Gate voltage. + fig, ax = plt.subplots() + for u_GS_filter in u_GS_unique: + myfilter = df['V_GB'].values == u_GS_filter + I_D=df['I_D'].values[myfilter] + gds=df['gds'].values[myfilter] + rds=1/gds + plt.semilogy(I_D, rds, label=f'U_GB={u_GS_filter}') + ax.set_xlabel('I_D') # Add an x-label to the axes. + ax.set_ylabel('1/gds') # Add a y-label to the axes. + ax.set_title("Lenght={:.2f}".format(r['param']['L'])) # Add a title to the axes. + ax.legend() # Add a legend. + file=os.path.join(workingDir, "gds vs ID, Lenght={:.2f}.png".format(r['param']['L'])) + plt.savefig(file) + plt.close(fig) + + # Third plot: For a given transistor length plot the intrinsic amplification as a function of the Gate voltage. + fig, ax = plt.subplots() + for u_GS_filter in u_GS_unique: + myfilter = df['V_GB'].values == u_GS_filter + I_D=df['I_D'].values[myfilter] + gds=df['gds'].values[myfilter] + gm=df['gm'].values[myfilter] + gain=gm/gds + plt.plot(I_D, gain, label=f'U_GB={u_GS_filter}') + ax.set_xlabel('I_D') # Add an x-label to the axes. + ax.set_ylabel('gm/gds') # Add a y-label to the axes. + ax.set_title("Lenght={:.2f}".format(r['param']['L'])) # Add a title to the axes. + ax.legend() # Add a legend. + file=os.path.join(workingDir, "gain vs ID, Lenght={:.2f}.png".format(r['param']['L'])) + plt.savefig(file) + plt.close(fig) + + # Fourth plot: For a given transistor length plot gm as a function of the Drain Source voltage. The gate voltage. + fig, ax = plt.subplots() + for u_GS_filter in u_GS_unique: + myfilter = df['V_GB'].values == u_GS_filter + V_DS=df['V_DS'].values[myfilter] + gds=df['gds'].values[myfilter] + gm=df['gm'].values[myfilter] + gain=gm/gds + ax.plot(V_DS, gm, label=f'U_GB={u_GS_filter}') + ax.set_xlabel('V_DS') # Add an x-label to the axes. + ax.set_ylabel('gm') # Add a y-label to the axes. + ax.set_title("Lenght={:.2f}".format(r['param']['L'])) # Add a title to the axes. + ax.legend() # Add a legend. + file=os.path.join(workingDir, "gm vs UDS, Lenght={:.2f}.png".format(r['param']['L'])) + plt.savefig(file) + plt.close(fig) + + # Fifth plot: For a given transistor length plot gds as a function of the Drain Source voltage. The gate voltage. + fig, ax = plt.subplots() + for u_GS_filter in u_GS_unique: + myfilter = df['V_GB'].values == u_GS_filter + V_DS=df['V_DS'].values[myfilter] + gds=df['gds'].values[myfilter] + gm=df['gm'].values[myfilter] + gain=gm/gds + plt.semilogy(V_DS, gds, label=f'U_GB={u_GS_filter}') + ax.set_xlabel('V_DS') # Add an x-label to the axes. + ax.set_ylabel('gds') # Add a y-label to the axes. + ax.set_title("Lenght={:.2f}".format(r['param']['L'])) # Add a title to the axes. + ax.legend() # Add a legend. + file=os.path.join(workingDir, "gds vs UDS, Lenght={:.2f}.png".format(r['param']['L'])) + plt.savefig(file) + plt.close(fig) + + # Sixth plot: For a given transistor length plot the intrinsic gain as a function of the Drain Source voltage. The gate voltage. + fig, ax = plt.subplots() + for u_GS_filter in u_GS_unique: + myfilter = df['V_GB'].values == u_GS_filter + V_DS=df['V_DS'].values[myfilter] + gds=df['gds'].values[myfilter] + gm=df['gm'].values[myfilter] + gain=gm/gds + plt.semilogy(V_DS, gain, label=f'U_GB={u_GS_filter}') + ax.set_xlabel('V_DS') # Add an x-label to the axes. + ax.set_ylabel('gm/gds') # Add a y-label to the axes. + ax.set_title("Lenght={:.2f}".format(r['param']['L'])) # Add a title to the axes. + ax.legend() # Add a legend. + file=os.path.join(workingDir, "gain, Lenght={:.2f}.png".format(r['param']['L'])) + plt.savefig(file) + plt.close(fig)
diff --git a/xschem/user_analog_project_wrapper.spice b/xschem/user_analog_project_wrapper.spice index 57be0cb..5f2f944 100644 --- a/xschem/user_analog_project_wrapper.spice +++ b/xschem/user_analog_project_wrapper.spice
@@ -1,92 +1,20 @@ - -* expanding symbol: user_analog_project_wrapper.sym # of pins=32 -** sym_path: /home/simon/code/caravel_tia/xschem/user_analog_project_wrapper.sym ** sch_path: /home/simon/code/caravel_tia/xschem/user_analog_project_wrapper.sch -.subckt user_analog_project_wrapper vdda1 vdda2 vssa1 vssa2 vccd1 vccd2 vssd1 vssd2 wb_clk_i -+ wb_rst_i wbs_stb_i wbs_cyc_i wbs_we_i wbs_sel_i[3] wbs_sel_i[2] wbs_sel_i[1] wbs_sel_i[0] wbs_dat_i[31] -+ wbs_dat_i[30] wbs_dat_i[29] wbs_dat_i[28] wbs_dat_i[27] wbs_dat_i[26] wbs_dat_i[25] wbs_dat_i[24] wbs_dat_i[23] -+ wbs_dat_i[22] wbs_dat_i[21] wbs_dat_i[20] wbs_dat_i[19] wbs_dat_i[18] wbs_dat_i[17] wbs_dat_i[16] wbs_dat_i[15] -+ wbs_dat_i[14] wbs_dat_i[13] wbs_dat_i[12] wbs_dat_i[11] wbs_dat_i[10] wbs_dat_i[9] wbs_dat_i[8] wbs_dat_i[7] -+ wbs_dat_i[6] wbs_dat_i[5] wbs_dat_i[4] wbs_dat_i[3] wbs_dat_i[2] wbs_dat_i[1] wbs_dat_i[0] wbs_adr_i[31] -+ wbs_adr_i[30] wbs_adr_i[29] wbs_adr_i[28] wbs_adr_i[27] wbs_adr_i[26] wbs_adr_i[25] wbs_adr_i[24] wbs_adr_i[23] -+ wbs_adr_i[22] wbs_adr_i[21] wbs_adr_i[20] wbs_adr_i[19] wbs_adr_i[18] wbs_adr_i[17] wbs_adr_i[16] wbs_adr_i[15] -+ wbs_adr_i[14] wbs_adr_i[13] wbs_adr_i[12] wbs_adr_i[11] wbs_adr_i[10] wbs_adr_i[9] wbs_adr_i[8] wbs_adr_i[7] -+ wbs_adr_i[6] wbs_adr_i[5] wbs_adr_i[4] wbs_adr_i[3] wbs_adr_i[2] wbs_adr_i[1] wbs_adr_i[0] wbs_ack_o -+ wbs_dat_o[31] wbs_dat_o[30] wbs_dat_o[29] wbs_dat_o[28] wbs_dat_o[27] wbs_dat_o[26] wbs_dat_o[25] wbs_dat_o[24] -+ wbs_dat_o[23] wbs_dat_o[22] wbs_dat_o[21] wbs_dat_o[20] wbs_dat_o[19] wbs_dat_o[18] wbs_dat_o[17] wbs_dat_o[16] -+ wbs_dat_o[15] wbs_dat_o[14] wbs_dat_o[13] wbs_dat_o[12] wbs_dat_o[11] wbs_dat_o[10] wbs_dat_o[9] wbs_dat_o[8] -+ wbs_dat_o[7] wbs_dat_o[6] wbs_dat_o[5] wbs_dat_o[4] wbs_dat_o[3] wbs_dat_o[2] wbs_dat_o[1] wbs_dat_o[0] -+ la_data_in[127] la_data_in[126] la_data_in[125] la_data_in[124] la_data_in[123] la_data_in[122] la_data_in[121] -+ la_data_in[120] la_data_in[119] la_data_in[118] la_data_in[117] la_data_in[116] la_data_in[115] la_data_in[114] -+ la_data_in[113] la_data_in[112] la_data_in[111] la_data_in[110] la_data_in[109] la_data_in[108] la_data_in[107] -+ la_data_in[106] la_data_in[105] la_data_in[104] la_data_in[103] la_data_in[102] la_data_in[101] la_data_in[100] -+ la_data_in[99] la_data_in[98] la_data_in[97] la_data_in[96] la_data_in[95] la_data_in[94] la_data_in[93] -+ la_data_in[92] la_data_in[91] la_data_in[90] la_data_in[89] la_data_in[88] la_data_in[87] la_data_in[86] -+ la_data_in[85] la_data_in[84] la_data_in[83] la_data_in[82] la_data_in[81] la_data_in[80] la_data_in[79] -+ la_data_in[78] la_data_in[77] la_data_in[76] la_data_in[75] la_data_in[74] la_data_in[73] la_data_in[72] -+ la_data_in[71] la_data_in[70] la_data_in[69] la_data_in[68] la_data_in[67] la_data_in[66] la_data_in[65] -+ la_data_in[64] la_data_in[63] la_data_in[62] la_data_in[61] la_data_in[60] la_data_in[59] la_data_in[58] -+ la_data_in[57] la_data_in[56] la_data_in[55] la_data_in[54] la_data_in[53] la_data_in[52] la_data_in[51] -+ la_data_in[50] la_data_in[49] la_data_in[48] la_data_in[47] la_data_in[46] la_data_in[45] la_data_in[44] -+ la_data_in[43] la_data_in[42] la_data_in[41] la_data_in[40] la_data_in[39] la_data_in[38] la_data_in[37] -+ la_data_in[36] la_data_in[35] la_data_in[34] la_data_in[33] la_data_in[32] la_data_in[31] la_data_in[30] -+ la_data_in[29] la_data_in[28] la_data_in[27] la_data_in[26] la_data_in[25] la_data_in[24] la_data_in[23] -+ la_data_in[22] la_data_in[21] la_data_in[20] la_data_in[19] la_data_in[18] la_data_in[17] la_data_in[16] -+ la_data_in[15] la_data_in[14] la_data_in[13] la_data_in[12] la_data_in[11] la_data_in[10] la_data_in[9] -+ la_data_in[8] la_data_in[7] la_data_in[6] la_data_in[5] la_data_in[4] la_data_in[3] la_data_in[2] la_data_in[1] -+ la_data_in[0] la_data_out[127] la_data_out[126] la_data_out[125] la_data_out[124] la_data_out[123] -+ la_data_out[122] la_data_out[121] la_data_out[120] la_data_out[119] la_data_out[118] la_data_out[117] -+ la_data_out[116] la_data_out[115] la_data_out[114] la_data_out[113] la_data_out[112] la_data_out[111] -+ la_data_out[110] la_data_out[109] la_data_out[108] la_data_out[107] la_data_out[106] la_data_out[105] -+ la_data_out[104] la_data_out[103] la_data_out[102] la_data_out[101] la_data_out[100] la_data_out[99] la_data_out[98] -+ la_data_out[97] la_data_out[96] la_data_out[95] la_data_out[94] la_data_out[93] la_data_out[92] la_data_out[91] -+ la_data_out[90] la_data_out[89] la_data_out[88] la_data_out[87] la_data_out[86] la_data_out[85] la_data_out[84] -+ la_data_out[83] la_data_out[82] la_data_out[81] la_data_out[80] la_data_out[79] la_data_out[78] la_data_out[77] -+ la_data_out[76] la_data_out[75] la_data_out[74] la_data_out[73] la_data_out[72] la_data_out[71] la_data_out[70] -+ la_data_out[69] la_data_out[68] la_data_out[67] la_data_out[66] la_data_out[65] la_data_out[64] la_data_out[63] -+ la_data_out[62] la_data_out[61] la_data_out[60] la_data_out[59] la_data_out[58] la_data_out[57] la_data_out[56] -+ la_data_out[55] la_data_out[54] la_data_out[53] la_data_out[52] la_data_out[51] la_data_out[50] la_data_out[49] -+ la_data_out[48] la_data_out[47] la_data_out[46] la_data_out[45] la_data_out[44] la_data_out[43] la_data_out[42] -+ la_data_out[41] la_data_out[40] la_data_out[39] la_data_out[38] la_data_out[37] la_data_out[36] la_data_out[35] -+ la_data_out[34] la_data_out[33] la_data_out[32] la_data_out[31] la_data_out[30] la_data_out[29] la_data_out[28] -+ la_data_out[27] la_data_out[26] la_data_out[25] la_data_out[24] la_data_out[23] la_data_out[22] la_data_out[21] -+ la_data_out[20] la_data_out[19] la_data_out[18] la_data_out[17] la_data_out[16] la_data_out[15] la_data_out[14] -+ la_data_out[13] la_data_out[12] la_data_out[11] la_data_out[10] la_data_out[9] la_data_out[8] la_data_out[7] -+ la_data_out[6] la_data_out[5] la_data_out[4] la_data_out[3] la_data_out[2] la_data_out[1] la_data_out[0] -+ la_oenb[127] la_oenb[126] la_oenb[125] la_oenb[124] la_oenb[123] la_oenb[122] la_oenb[121] la_oenb[120] -+ la_oenb[119] la_oenb[118] la_oenb[117] la_oenb[116] la_oenb[115] la_oenb[114] la_oenb[113] la_oenb[112] -+ la_oenb[111] la_oenb[110] la_oenb[109] la_oenb[108] la_oenb[107] la_oenb[106] la_oenb[105] la_oenb[104] -+ la_oenb[103] la_oenb[102] la_oenb[101] la_oenb[100] la_oenb[99] la_oenb[98] la_oenb[97] la_oenb[96] la_oenb[95] -+ la_oenb[94] la_oenb[93] la_oenb[92] la_oenb[91] la_oenb[90] la_oenb[89] la_oenb[88] la_oenb[87] la_oenb[86] -+ la_oenb[85] la_oenb[84] la_oenb[83] la_oenb[82] la_oenb[81] la_oenb[80] la_oenb[79] la_oenb[78] la_oenb[77] -+ la_oenb[76] la_oenb[75] la_oenb[74] la_oenb[73] la_oenb[72] la_oenb[71] la_oenb[70] la_oenb[69] la_oenb[68] -+ la_oenb[67] la_oenb[66] la_oenb[65] la_oenb[64] la_oenb[63] la_oenb[62] la_oenb[61] la_oenb[60] la_oenb[59] -+ la_oenb[58] la_oenb[57] la_oenb[56] la_oenb[55] la_oenb[54] la_oenb[53] la_oenb[52] la_oenb[51] la_oenb[50] -+ la_oenb[49] la_oenb[48] la_oenb[47] la_oenb[46] la_oenb[45] la_oenb[44] la_oenb[43] la_oenb[42] la_oenb[41] -+ la_oenb[40] la_oenb[39] la_oenb[38] la_oenb[37] la_oenb[36] la_oenb[35] la_oenb[34] la_oenb[33] la_oenb[32] -+ la_oenb[31] la_oenb[30] la_oenb[29] la_oenb[28] la_oenb[27] la_oenb[26] la_oenb[25] la_oenb[24] la_oenb[23] -+ la_oenb[22] la_oenb[21] la_oenb[20] la_oenb[19] la_oenb[18] la_oenb[17] la_oenb[16] la_oenb[15] la_oenb[14] -+ la_oenb[13] la_oenb[12] la_oenb[11] la_oenb[10] la_oenb[9] la_oenb[8] la_oenb[7] la_oenb[6] la_oenb[5] -+ la_oenb[4] la_oenb[3] la_oenb[2] la_oenb[1] la_oenb[0] io_in[26] io_in[25] io_in[24] io_in[23] io_in[22] -+ io_in[21] io_in[20] io_in[19] io_in[18] io_in[17] io_in[16] io_in[15] io_in[14] io_in[13] io_in[12] io_in[11] -+ io_in[10] io_in[9] io_in[8] io_in[7] io_in[6] io_in[5] io_in[4] io_in[3] io_in[2] io_in[1] io_in[0] -+ io_in_3v3[26] io_in_3v3[25] io_in_3v3[24] io_in_3v3[23] io_in_3v3[22] io_in_3v3[21] io_in_3v3[20] io_in_3v3[19] -+ io_in_3v3[18] io_in_3v3[17] io_in_3v3[16] io_in_3v3[15] io_in_3v3[14] io_in_3v3[13] io_in_3v3[12] io_in_3v3[11] -+ io_in_3v3[10] io_in_3v3[9] io_in_3v3[8] io_in_3v3[7] io_in_3v3[6] io_in_3v3[5] io_in_3v3[4] io_in_3v3[3] -+ io_in_3v3[2] io_in_3v3[1] io_in_3v3[0] io_out[26] io_out[25] io_out[24] io_out[23] io_out[22] io_out[21] -+ io_out[20] io_out[19] io_out[18] io_out[17] io_out[16] io_out[15] io_out[14] io_out[13] io_out[12] io_out[11] -+ io_out[10] io_out[9] io_out[8] io_out[7] io_out[6] io_out[5] io_out[4] io_out[3] io_out[2] io_out[1] io_out[0] -+ io_oeb[26] io_oeb[25] io_oeb[24] io_oeb[23] io_oeb[22] io_oeb[21] io_oeb[20] io_oeb[19] io_oeb[18] io_oeb[17] -+ io_oeb[16] io_oeb[15] io_oeb[14] io_oeb[13] io_oeb[12] io_oeb[11] io_oeb[10] io_oeb[9] io_oeb[8] io_oeb[7] -+ io_oeb[6] io_oeb[5] io_oeb[4] io_oeb[3] io_oeb[2] io_oeb[1] io_oeb[0] gpio_analog[17] gpio_analog[16] -+ gpio_analog[15] gpio_analog[14] gpio_analog[13] gpio_analog[12] gpio_analog[11] gpio_analog[10] gpio_analog[9] -+ gpio_analog[8] gpio_analog[7] gpio_analog[6] gpio_analog[5] gpio_analog[4] gpio_analog[3] gpio_analog[2] -+ gpio_analog[1] gpio_analog[0] gpio_noesd[17] gpio_noesd[16] gpio_noesd[15] gpio_noesd[14] gpio_noesd[13] -+ gpio_noesd[12] gpio_noesd[11] gpio_noesd[10] gpio_noesd[9] gpio_noesd[8] gpio_noesd[7] gpio_noesd[6] gpio_noesd[5] -+ gpio_noesd[4] gpio_noesd[3] gpio_noesd[2] gpio_noesd[1] gpio_noesd[0] io_analog[10] io_analog[9] io_analog[8] -+ io_analog[7] io_analog[6] io_analog[5] io_analog[4] io_analog[3] io_analog[2] io_analog[1] io_analog[0] -+ io_clamp_high[2] io_clamp_high[1] io_clamp_high[0] io_clamp_low[2] io_clamp_low[1] io_clamp_low[0] user_clock2 -+ user_irq[2] user_irq[1] user_irq[0] +**.subckt user_analog_project_wrapper vdda1 vdda2 vssa1 vssa2 vccd1 vccd2 vssd1 vssd2 wb_clk_i +*+ wb_rst_i wbs_stb_i wbs_cyc_i wbs_we_i wbs_sel_i[3],wbs_sel_i[2],wbs_sel_i[1],wbs_sel_i[0] +*+ wbs_dat_i[31],wbs_dat_i[30],wbs_dat_i[29],wbs_dat_i[28],wbs_dat_i[27],wbs_dat_i[26],wbs_dat_i[25],wbs_dat_i[24],wbs_dat_i[23],wbs_dat_i[22],wbs_dat_i[21],wbs_dat_i[20],wbs_dat_i[19],wbs_dat_i[18],wbs_dat_i[17],wbs_dat_i[16],wbs_dat_i[15],wbs_dat_i[14],wbs_dat_i[13],wbs_dat_i[12],wbs_dat_i[11],wbs_dat_i[10],wbs_dat_i[9],wbs_dat_i[8],wbs_dat_i[7],wbs_dat_i[6],wbs_dat_i[5],wbs_dat_i[4],wbs_dat_i[3],wbs_dat_i[2],wbs_dat_i[1],wbs_dat_i[0] +*+ wbs_adr_i[31],wbs_adr_i[30],wbs_adr_i[29],wbs_adr_i[28],wbs_adr_i[27],wbs_adr_i[26],wbs_adr_i[25],wbs_adr_i[24],wbs_adr_i[23],wbs_adr_i[22],wbs_adr_i[21],wbs_adr_i[20],wbs_adr_i[19],wbs_adr_i[18],wbs_adr_i[17],wbs_adr_i[16],wbs_adr_i[15],wbs_adr_i[14],wbs_adr_i[13],wbs_adr_i[12],wbs_adr_i[11],wbs_adr_i[10],wbs_adr_i[9],wbs_adr_i[8],wbs_adr_i[7],wbs_adr_i[6],wbs_adr_i[5],wbs_adr_i[4],wbs_adr_i[3],wbs_adr_i[2],wbs_adr_i[1],wbs_adr_i[0] wbs_ack_o +*+ wbs_dat_o[31],wbs_dat_o[30],wbs_dat_o[29],wbs_dat_o[28],wbs_dat_o[27],wbs_dat_o[26],wbs_dat_o[25],wbs_dat_o[24],wbs_dat_o[23],wbs_dat_o[22],wbs_dat_o[21],wbs_dat_o[20],wbs_dat_o[19],wbs_dat_o[18],wbs_dat_o[17],wbs_dat_o[16],wbs_dat_o[15],wbs_dat_o[14],wbs_dat_o[13],wbs_dat_o[12],wbs_dat_o[11],wbs_dat_o[10],wbs_dat_o[9],wbs_dat_o[8],wbs_dat_o[7],wbs_dat_o[6],wbs_dat_o[5],wbs_dat_o[4],wbs_dat_o[3],wbs_dat_o[2],wbs_dat_o[1],wbs_dat_o[0] +*+ la_data_in[127],la_data_in[126],la_data_in[125],la_data_in[124],la_data_in[123],la_data_in[122],la_data_in[121],la_data_in[120],la_data_in[119],la_data_in[118],la_data_in[117],la_data_in[116],la_data_in[115],la_data_in[114],la_data_in[113],la_data_in[112],la_data_in[111],la_data_in[110],la_data_in[109],la_data_in[108],la_data_in[107],la_data_in[106],la_data_in[105],la_data_in[104],la_data_in[103],la_data_in[102],la_data_in[101],la_data_in[100],la_data_in[99],la_data_in[98],la_data_in[97],la_data_in[96],la_data_in[95],la_data_in[94],la_data_in[93],la_data_in[92],la_data_in[91],la_data_in[90],la_data_in[89],la_data_in[88],la_data_in[87],la_data_in[86],la_data_in[85],la_data_in[84],la_data_in[83],la_data_in[82],la_data_in[81],la_data_in[80],la_data_in[79],la_data_in[78],la_data_in[77],la_data_in[76],la_data_in[75],la_data_in[74],la_data_in[73],la_data_in[72],la_data_in[71],la_data_in[70],la_data_in[69],la_data_in[68],la_data_in[67],la_data_in[66],la_data_in[65],la_data_in[64],la_data_in[63],la_data_in[62],la_data_in[61],la_data_in[60],la_data_in[59],la_data_in[58],la_data_in[57],la_data_in[56],la_data_in[55],la_data_in[54],la_data_in[53],la_data_in[52],la_data_in[51],la_data_in[50],la_data_in[49],la_data_in[48],la_data_in[47],la_data_in[46],la_data_in[45],la_data_in[44],la_data_in[43],la_data_in[42],la_data_in[41],la_data_in[40],la_data_in[39],la_data_in[38],la_data_in[37],la_data_in[36],la_data_in[35],la_data_in[34],la_data_in[33],la_data_in[32],la_data_in[31],la_data_in[30],la_data_in[29],la_data_in[28],la_data_in[27],la_data_in[26],la_data_in[25],la_data_in[24],la_data_in[23],la_data_in[22],la_data_in[21],la_data_in[20],la_data_in[19],la_data_in[18],la_data_in[17],la_data_in[16],la_data_in[15],la_data_in[14],la_data_in[13],la_data_in[12],la_data_in[11],la_data_in[10],la_data_in[9],la_data_in[8],la_data_in[7],la_data_in[6],la_data_in[5],la_data_in[4],la_data_in[3],la_data_in[2],la_data_in[1],la_data_in[0] +*+ la_data_out[127],la_data_out[126],la_data_out[125],la_data_out[124],la_data_out[123],la_data_out[122],la_data_out[121],la_data_out[120],la_data_out[119],la_data_out[118],la_data_out[117],la_data_out[116],la_data_out[115],la_data_out[114],la_data_out[113],la_data_out[112],la_data_out[111],la_data_out[110],la_data_out[109],la_data_out[108],la_data_out[107],la_data_out[106],la_data_out[105],la_data_out[104],la_data_out[103],la_data_out[102],la_data_out[101],la_data_out[100],la_data_out[99],la_data_out[98],la_data_out[97],la_data_out[96],la_data_out[95],la_data_out[94],la_data_out[93],la_data_out[92],la_data_out[91],la_data_out[90],la_data_out[89],la_data_out[88],la_data_out[87],la_data_out[86],la_data_out[85],la_data_out[84],la_data_out[83],la_data_out[82],la_data_out[81],la_data_out[80],la_data_out[79],la_data_out[78],la_data_out[77],la_data_out[76],la_data_out[75],la_data_out[74],la_data_out[73],la_data_out[72],la_data_out[71],la_data_out[70],la_data_out[69],la_data_out[68],la_data_out[67],la_data_out[66],la_data_out[65],la_data_out[64],la_data_out[63],la_data_out[62],la_data_out[61],la_data_out[60],la_data_out[59],la_data_out[58],la_data_out[57],la_data_out[56],la_data_out[55],la_data_out[54],la_data_out[53],la_data_out[52],la_data_out[51],la_data_out[50],la_data_out[49],la_data_out[48],la_data_out[47],la_data_out[46],la_data_out[45],la_data_out[44],la_data_out[43],la_data_out[42],la_data_out[41],la_data_out[40],la_data_out[39],la_data_out[38],la_data_out[37],la_data_out[36],la_data_out[35],la_data_out[34],la_data_out[33],la_data_out[32],la_data_out[31],la_data_out[30],la_data_out[29],la_data_out[28],la_data_out[27],la_data_out[26],la_data_out[25],la_data_out[24],la_data_out[23],la_data_out[22],la_data_out[21],la_data_out[20],la_data_out[19],la_data_out[18],la_data_out[17],la_data_out[16],la_data_out[15],la_data_out[14],la_data_out[13],la_data_out[12],la_data_out[11],la_data_out[10],la_data_out[9],la_data_out[8],la_data_out[7],la_data_out[6],la_data_out[5],la_data_out[4],la_data_out[3],la_data_out[2],la_data_out[1],la_data_out[0] +*+ io_in[26],io_in[25],io_in[24],io_in[23],io_in[22],io_in[21],io_in[20],io_in[19],io_in[18],io_in[17],io_in[16],io_in[15],io_in[14],io_in[13],io_in[12],io_in[11],io_in[10],io_in[9],io_in[8],io_in[7],io_in[6],io_in[5],io_in[4],io_in[3],io_in[2],io_in[1],io_in[0] +*+ io_in_3v3[26],io_in_3v3[25],io_in_3v3[24],io_in_3v3[23],io_in_3v3[22],io_in_3v3[21],io_in_3v3[20],io_in_3v3[19],io_in_3v3[18],io_in_3v3[17],io_in_3v3[16],io_in_3v3[15],io_in_3v3[14],io_in_3v3[13],io_in_3v3[12],io_in_3v3[11],io_in_3v3[10],io_in_3v3[9],io_in_3v3[8],io_in_3v3[7],io_in_3v3[6],io_in_3v3[5],io_in_3v3[4],io_in_3v3[3],io_in_3v3[2],io_in_3v3[1],io_in_3v3[0] user_clock2 +*+ io_out[26],io_out[25],io_out[24],io_out[23],io_out[22],io_out[21],io_out[20],io_out[19],io_out[18],io_out[17],io_out[16],io_out[15],io_out[14],io_out[13],io_out[12],io_out[11],io_out[10],io_out[9],io_out[8],io_out[7],io_out[6],io_out[5],io_out[4],io_out[3],io_out[2],io_out[1],io_out[0] +*+ io_oeb[26],io_oeb[25],io_oeb[24],io_oeb[23],io_oeb[22],io_oeb[21],io_oeb[20],io_oeb[19],io_oeb[18],io_oeb[17],io_oeb[16],io_oeb[15],io_oeb[14],io_oeb[13],io_oeb[12],io_oeb[11],io_oeb[10],io_oeb[9],io_oeb[8],io_oeb[7],io_oeb[6],io_oeb[5],io_oeb[4],io_oeb[3],io_oeb[2],io_oeb[1],io_oeb[0] +*+ gpio_analog[17],gpio_analog[16],gpio_analog[15],gpio_analog[14],gpio_analog[13],gpio_analog[12],gpio_analog[11],gpio_analog[10],gpio_analog[9],gpio_analog[8],gpio_analog[7],gpio_analog[6],gpio_analog[5],gpio_analog[4],gpio_analog[3],gpio_analog[2],gpio_analog[1],gpio_analog[0] +*+ gpio_noesd[17],gpio_noesd[16],gpio_noesd[15],gpio_noesd[14],gpio_noesd[13],gpio_noesd[12],gpio_noesd[11],gpio_noesd[10],gpio_noesd[9],gpio_noesd[8],gpio_noesd[7],gpio_noesd[6],gpio_noesd[5],gpio_noesd[4],gpio_noesd[3],gpio_noesd[2],gpio_noesd[1],gpio_noesd[0] +*+ io_analog[10],io_analog[9],io_analog[8],io_analog[7],io_analog[6],io_analog[5],io_analog[4],io_analog[3],io_analog[2],io_analog[1],io_analog[0] io_clamp_high[2],io_clamp_high[1],io_clamp_high[0] io_clamp_low[2],io_clamp_low[1],io_clamp_low[0] +*+ user_irq[2],user_irq[1],user_irq[0] +*+ la_oenb[127],la_oenb[126],la_oenb[125],la_oenb[124],la_oenb[123],la_oenb[122],la_oenb[121],la_oenb[120],la_oenb[119],la_oenb[118],la_oenb[117],la_oenb[116],la_oenb[115],la_oenb[114],la_oenb[113],la_oenb[112],la_oenb[111],la_oenb[110],la_oenb[109],la_oenb[108],la_oenb[107],la_oenb[106],la_oenb[105],la_oenb[104],la_oenb[103],la_oenb[102],la_oenb[101],la_oenb[100],la_oenb[99],la_oenb[98],la_oenb[97],la_oenb[96],la_oenb[95],la_oenb[94],la_oenb[93],la_oenb[92],la_oenb[91],la_oenb[90],la_oenb[89],la_oenb[88],la_oenb[87],la_oenb[86],la_oenb[85],la_oenb[84],la_oenb[83],la_oenb[82],la_oenb[81],la_oenb[80],la_oenb[79],la_oenb[78],la_oenb[77],la_oenb[76],la_oenb[75],la_oenb[74],la_oenb[73],la_oenb[72],la_oenb[71],la_oenb[70],la_oenb[69],la_oenb[68],la_oenb[67],la_oenb[66],la_oenb[65],la_oenb[64],la_oenb[63],la_oenb[62],la_oenb[61],la_oenb[60],la_oenb[59],la_oenb[58],la_oenb[57],la_oenb[56],la_oenb[55],la_oenb[54],la_oenb[53],la_oenb[52],la_oenb[51],la_oenb[50],la_oenb[49],la_oenb[48],la_oenb[47],la_oenb[46],la_oenb[45],la_oenb[44],la_oenb[43],la_oenb[42],la_oenb[41],la_oenb[40],la_oenb[39],la_oenb[38],la_oenb[37],la_oenb[36],la_oenb[35],la_oenb[34],la_oenb[33],la_oenb[32],la_oenb[31],la_oenb[30],la_oenb[29],la_oenb[28],la_oenb[27],la_oenb[26],la_oenb[25],la_oenb[24],la_oenb[23],la_oenb[22],la_oenb[21],la_oenb[20],la_oenb[19],la_oenb[18],la_oenb[17],la_oenb[16],la_oenb[15],la_oenb[14],la_oenb[13],la_oenb[12],la_oenb[11],la_oenb[10],la_oenb[9],la_oenb[8],la_oenb[7],la_oenb[6],la_oenb[5],la_oenb[4],la_oenb[3],la_oenb[2],la_oenb[1],la_oenb[0] *.iopin vdda1 *.iopin vdda2 *.iopin vssa1 @@ -145,5 +73,433 @@ R3 vccd1 io_clamp_high[1] sky130_fd_pr__res_generic_m3 W=11 L=0.25 m=1 R4 vssd1 io_clamp_low[2] sky130_fd_pr__res_generic_m3 W=11 L=0.25 m=1 R6 vccd1 io_clamp_high[2] sky130_fd_pr__res_generic_m3 W=11 L=0.25 m=1 +**.ends + +* expanding symbol: esd/esd_diodes.sym # of pins=3 +** sym_path: /home/simon/code/caravel_tia/xschem/esd/esd_diodes.sym +** sch_path: /home/simon/code/caravel_tia/xschem/esd/esd_diodes.sch +.subckt esd_diodes VP io VN +*.iopin VP +*.iopin io +*.iopin VN +D1 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D2 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D3 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D4 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D5 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D6 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D7 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D8 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D9 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D10 io VP sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D11 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D12 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D13 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D14 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D15 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D16 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D17 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D18 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D19 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 +D20 VN io sky130_fd_pr__diode_pw2nd_05v5 area=4e12 .ends + +* expanding symbol: mpw5_submission.sym # of pins=7 +** sym_path: /home/simon/code/caravel_tia/xschem/mpw5_submission.sym +** sch_path: /home/simon/code/caravel_tia/xschem/mpw5_submission.sch +.subckt mpw5_submission VP I_out Dis_TIA In_TIA Out_N Out_P VN +*.iopin VP +*.ipin In_TIA +*.opin Out_N +*.iopin VN +*.opin Out_P +*.ipin Dis_TIA +*.opin I_out +x4 VP Out_N Out_P net2 net3 net4 VN outdriver +x5 VP net9 I_out net8 net7 net10 net11 net12 net13 net14 VN current_mirrorx8 +x6 VP net8 VN low_pvt_source +x7 VP net1 net2 net5 net7 VN current_mirror_channel +x8 VP net6 net4 net3 Dis_TIA In_TIA net1 VN tia_rgc_core +.ends + + +* expanding symbol: outdriver/outdriver.sym # of pins=7 +** sym_path: /home/simon/code/caravel_tia/xschem/outdriver/outdriver.sym +** sch_path: /home/simon/code/caravel_tia/xschem/outdriver/outdriver.sch +.subckt outdriver VP OutputN OutputP I_Bias InputSignal InputRef VN +*.iopin VN +*.ipin InputRef +*.iopin VP +*.ipin I_Bias +*.ipin InputSignal +*.opin OutputN +*.opin OutputP +XM1 V_da1_P InputSignal VM6D VM6D sky130_fd_pr__nfet_01v8_lvt L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=11*2 m=11*2 +XM3 VM3D I_Bias net4 VN sky130_fd_pr__nfet_01v8 L=0.5 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2*(64) m=2*(64) +XM6 VM6D I_Bias VM3D VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=64 m=64 +XM7 I_Bias I_Bias net1 VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2 m=2 +XM8 net1 I_Bias VN VN sky130_fd_pr__nfet_01v8 L=0.5 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2*2 m=2*2 +XM2 V_da2_P V_da1_P VM1_D VM1_D sky130_fd_pr__nfet_01v8_lvt L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=44*2 m=44*2 +XM4 V_da2_N V_da1_N VM1_D VM1_D sky130_fd_pr__nfet_01v8_lvt L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=44*2 m=44*2 +XM9 net2 I_Bias net5 VN sky130_fd_pr__nfet_01v8 L=0.5 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2*(256+64) m=2*(256+64) +XM10 VM1_D I_Bias net2 VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=256+64 m=256+64 +XM11 OutputP V_da2_P VM14D VM14D sky130_fd_pr__nfet_01v8_lvt L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=44*4*2 m=44*4*2 +XM12 OutputN V_da2_N VM14D VM14D sky130_fd_pr__nfet_01v8_lvt L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=44*4*2 m=44*4*2 +XM13 net3 I_Bias net6 VN sky130_fd_pr__nfet_01v8 L=0.5 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2*(1024+256) m=2*(1024+256) +XM14 VM14D I_Bias net3 VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=1024+256 m=1024+256 +XR7 V_da2_N VP VP sky130_fd_pr__res_high_po_5p73 L=4 mult=4*2 m=4*2 +XR5 V_da2_P VP VP sky130_fd_pr__res_high_po_5p73 L=4 mult=4*2 m=4*2 +XR3 V_da1_N VP VP sky130_fd_pr__res_high_po_2p85 L=6 mult=4 m=4 +XR4 V_da1_P VP VP sky130_fd_pr__res_high_po_2p85 L=6 mult=4 m=4 +XC2 InputRef VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XM5 V_da1_N InputRef VM6D VM6D sky130_fd_pr__nfet_01v8_lvt L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=11*2 m=11*2 +XR8 OutputP VP VP sky130_fd_pr__res_high_po_5p73 L=4 mult=16*2 m=16*2 +XC1 InputRef VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC6 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC8 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +V1 net4 VN 0 +V2 net5 VN 0 +V3 net6 VN 0 +XR1 OutputN VP VP sky130_fd_pr__res_high_po_5p73 L=4 mult=16*2 m=16*2 +XC7 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC9 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC10 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC11 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC12 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC13 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC14 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC5 I_Bias VN sky130_fd_pr__cap_mim_m3_1 W=20 L=20 MF=1 m=1 +XC3 I_Bias VN sky130_fd_pr__cap_mim_m3_1 W=20 L=20 MF=1 m=1 +.ends + + +* expanding symbol: bias/current_mirrorx8.sym # of pins=11 +** sym_path: /home/simon/code/caravel_tia/xschem/bias/current_mirrorx8.sym +** sch_path: /home/simon/code/caravel_tia/xschem/bias/current_mirrorx8.sch +.subckt current_mirrorx8 VP I_out_3 I_out_1 I_In I_out_0 I_out_4 I_out_5 I_out_6 I_out_7 I_out_2 VN +*.opin I_out_0 +*.ipin I_In +*.iopin VP +*.opin I_out_1 +*.opin I_out_2 +*.opin I_out_3 +*.opin I_out_4 +*.opin I_out_5 +*.opin I_out_6 +*.opin I_out_7 +*.iopin VN +XM1 net1 I_In VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM2 I_In I_In net1 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XC1 I_In VP sky130_fd_pr__cap_mim_m3_1 W=20 L=20 MF=1 m=1 +XC2 VP I_In sky130_fd_pr__cap_mim_m3_2 W=20 L=20 MF=1 m=1 +XC4 VP VN VN sky130_fd_pr__cap_var_lvt W=2 L=5 VM=5 +XC3 I_In VN VN sky130_fd_pr__cap_var_lvt W=2 L=5 VM=5 +XM3 net2 I_In VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM4 I_out_0 I_In net2 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XM5 net3 I_In VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM6 I_out_1 I_In net3 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XM7 net4 I_In VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM8 I_out_2 I_In net4 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XM9 net5 I_In VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM10 I_out_3 I_In net5 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XM11 net6 I_In VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM12 I_out_4 I_In net6 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XM13 net7 I_In VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM14 I_out_5 I_In net7 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XM15 net8 I_In VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM16 I_out_6 I_In net8 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XM17 net9 I_In VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM18 I_out_7 I_In net9 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +.ends + + +* expanding symbol: bias/low_pvt_source.sym # of pins=3 +** sym_path: /home/simon/code/caravel_tia/xschem/bias/low_pvt_source.sym +** sch_path: /home/simon/code/caravel_tia/xschem/bias/low_pvt_source.sch +.subckt low_pvt_source VP I_ref VN +*.iopin VP +*.opin I_ref +*.iopin VN +XM1 VM1D VM8D VP VP sky130_fd_pr__pfet_01v8 L=1 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=10 m=10 +XM2 VM2D VM2D VN VN sky130_fd_pr__nfet_01v8 L=6 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=30 m=30 +XM5 VM9D VM8D VM1D VP sky130_fd_pr__pfet_01v8 L=0.2 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2 m=2 +XM11 VM11D VM2D VM12D VN sky130_fd_pr__nfet_01v8 L=6 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=65 m=65 +XM12 VM12D VM12G VN VN sky130_fd_pr__nfet_01v8 L=6 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2 m=2 +XM14 VM14D VM12G VN VN sky130_fd_pr__nfet_01v8 L=6 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2 m=2 +XM16 VM16D VM8D VP VP sky130_fd_pr__pfet_01v8 L=1 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=10 m=10 +XM17 VM8D VM8D VM16D VP sky130_fd_pr__pfet_01v8 L=0.2 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2 m=2 +XM18 VM18D VM8D VP VP sky130_fd_pr__pfet_01v8 L=1 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=10*3*2 m=10*3*2 +XM19 VM14D VM8D VM18D VP sky130_fd_pr__pfet_01v8 L=0.2 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2*3*2 m=2*3*2 +XM20 VM20D VM8D VP VP sky130_fd_pr__pfet_01v8 L=1 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=10*1*1 m=10*1*1 +XM21 VM22D VM8D VM20D VP sky130_fd_pr__pfet_01v8 L=0.2 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2*1*1 m=2*1*1 +XM22 VM22D VM4S VM3D VN sky130_fd_pr__nfet_01v8 L=6 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=20 m=20 +XM3 VM3D VM3G VN VN sky130_fd_pr__nfet_01v8 L=6 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XM13 VP VM14D VM12G VM12G sky130_fd_pr__nfet_01v8_lvt L=0.15 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=20 m=20 +XM4 I_ref VM22D VM4S VN sky130_fd_pr__nfet_01v8_lvt L=0.15 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=20 m=20 +XM48 VM50D VM11D VP VP sky130_fd_pr__pfet_01v8 L=2 W=0.5 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=1 m=1 +XM50 VM50D VM11D VN VN sky130_fd_pr__nfet_01v8_lvt L=0.2 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=10 m=10 +XC3 VP VM8D sky130_fd_pr__cap_mim_m3_1 W=20 L=20 MF=1 m=1 +XM8 VM8D VM9D VM11D VM11D sky130_fd_pr__nfet_01v8_lvt L=1 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=20 m=20 +XM9 VM9D VM9D VM2D VM2D sky130_fd_pr__nfet_01v8_lvt L=1 W=4 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=20 m=20 +XM10 VM8D VM50D VN VN sky130_fd_pr__nfet_01v8 L=2 W=0.5 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=1 m=1 +XC4 VN VP sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XR7 net3 VM4S VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR3 net4 net3 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR4 net2 net4 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR5 net1 net2 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR6 VN net1 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR8 net5 VM12G VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR1 net5 VM3G VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR2 net7 net6 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR9 VN net6 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR10 net11 net7 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR11 net12 net11 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR12 net8 net12 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR13 net10 VM3G VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR14 net9 net10 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XR15 net8 net9 VN sky130_fd_pr__res_xhigh_po_1p41 L=10 mult=1 m=1 +XC1 VN VP sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +.ends + + +* expanding symbol: bias/current_mirror_channel.sym # of pins=6 +** sym_path: /home/simon/code/caravel_tia/xschem/bias/current_mirror_channel.sym +** sch_path: /home/simon/code/caravel_tia/xschem/bias/current_mirror_channel.sch +.subckt current_mirror_channel VP TIA_I_Bias1 A_Out_I_Bias TIA_I_Bias2 I_in_channel VN +*.ipin I_in_channel +*.iopin VP +*.opin TIA_I_Bias2 +*.iopin VN +*.opin TIA_I_Bias1 +*.opin A_Out_I_Bias +XM1 I_in_channel I_in_channel net1 VN sky130_fd_pr__nfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=2 m=2 +XM2 net5 net4 VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4 m=4*4 +XM3 net1 I_in_channel VN VN sky130_fd_pr__nfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2*4 m=2*4 +XM4 net4 net4 net5 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4 m=4 +XC8 net4 VP sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XM5 net4 I_in_channel net2 VN sky130_fd_pr__nfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=2 m=2 +XM6 TIA_I_Bias2 I_in_channel net3 VN sky130_fd_pr__nfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=2 m=2 +XM7 net2 I_in_channel VN VN sky130_fd_pr__nfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2*4 m=2*4 +XM8 net3 I_in_channel VN VN sky130_fd_pr__nfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=2*4 m=2*4 +XM13 net6 net4 VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4*10 m=4*4*10 +XM14 TIA_I_Bias1 net4 net6 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=4*10 m=4*10 +XM9 net7 net4 VP VP sky130_fd_pr__pfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=4*4*12 m=4*4*12 +XM10 A_Out_I_Bias net4 net7 VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=4*12 m=4*12 +XC4 net4 VP sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC3 VN I_in_channel sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC6 VN I_in_channel sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC1 VN net4 sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC2 VN VP sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +.ends + + +* expanding symbol: tia/tia_rgc_core.sym # of pins=8 +** sym_path: /home/simon/code/caravel_tia/xschem/tia/tia_rgc_core.sym +** sch_path: /home/simon/code/caravel_tia/xschem/tia/tia_rgc_core.sch +.subckt tia_rgc_core VP Out_2 Out_ref Out_1 Disable_TIA Input I_Bias1 VN +*.iopin VN +*.iopin VP +*.opin Out_2 +*.ipin I_Bias1 +*.ipin Input +*.ipin Disable_TIA +*.opin Out_1 +*.opin Out_ref +XM5 VM5D I_Bias1 VN VN sky130_fd_pr__nfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=6 m=6 +XM6 VM6D I_Bias1 VN VN sky130_fd_pr__nfet_01v8 L=1.0 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=6 m=6 +XM7 Out_1 Input VM28D VN sky130_fd_pr__nfet_01v8_lvt L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=100 m=100 +XM8 Out_1 Input VP VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=60 m=60 +XM9 Input I_Bias1 VM5D VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=12 m=12 +XM10 I_Bias1 I_Bias1 VM6D VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=12 m=12 +XM23 I_Bias1 Disable_TIA VN VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=5 m=5 +XM26 Disable_TIA_B Disable_TIA VN VN sky130_fd_pr__nfet_01v8 L=1 W=0.5 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=1 m=1 +XM27 Disable_TIA_B Disable_TIA VP VP sky130_fd_pr__pfet_01v8 L=1 W=0.5 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=1 m=1 +XM28 VM28D Disable_TIA_B VN VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=100 m=100 +XM36 VM36D I_Bias1 VN VN sky130_fd_pr__nfet_01v8 L=1 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=6 m=6 +XM37 Out_ref VM39D VM40D VN sky130_fd_pr__nfet_01v8_lvt L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=100 m=100 +XM38 Out_ref VM39D VP VP sky130_fd_pr__pfet_01v8 L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=60 m=60 +XM39 VM39D I_Bias1 VM36D VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=12 m=12 +XM40 VM40D Disable_TIA_B VN VN sky130_fd_pr__nfet_01v8 L=0.15 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=100 m=100 +XC20 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XM16 Out_2 VN VP VP sky130_fd_pr__pfet_01v8 L=0.5 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=10 m=10 +XM15 Out_2 Out_1 Input Input sky130_fd_pr__nfet_01v8_lvt L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=30 m=30 +XM31 VM31D Out_ref VM39D VM39D sky130_fd_pr__nfet_01v8_lvt L=0.2 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' ++ as='int((nf+2)/2) * W/nf * 0.29' pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' ++ nrd='0.29 / W' nrs='0.29 / W' sa=0 sb=0 sd=0 mult=30 m=30 +XM35 VM31D VN VP VP sky130_fd_pr__pfet_01v8 L=0.5 W=2 nf=1 ad='int((nf+1)/2) * W/nf * 0.29' as='int((nf+2)/2) * W/nf * 0.29' ++ pd='2*int((nf+1)/2) * (W/nf + 0.29)' ps='2*int((nf+2)/2) * (W/nf + 0.29)' nrd='0.29 / W' nrs='0.29 / W' ++ sa=0 sb=0 sd=0 mult=10 m=10 +XC7 VP VN sky130_fd_pr__cap_mim_m3_2 W=30 L=30 MF=1 m=1 +XC2 VP VM28D sky130_fd_pr__cap_mim_m3_2 W=20 L=30 MF=1 m=1 +XC5 VN I_Bias1 sky130_fd_pr__cap_mim_m3_1 W=15 L=12 MF=1 m=1 +XC4 VN I_Bias1 sky130_fd_pr__cap_mim_m3_1 W=15 L=12 MF=1 m=1 +XC1 VP VM40D sky130_fd_pr__cap_mim_m3_2 W=20 L=30 MF=1 m=1 +XC3 Disable_TIA_B VN VN sky130_fd_pr__cap_var_lvt W=2 L=5 VM=5 +.ends + +.end