diff --git a/README.rst b/README.rst
index dae226e73..589c70d3d 100644
--- a/README.rst
+++ b/README.rst
@@ -10,6 +10,7 @@ OpenFASoC
OpenFASOC is focused on open-source automated analog generation from user specification to GDSII with fully open-sourced tools.
This project is led by a team of researchers at the University of Michigan and is inspired by FASoC, that sits on proprietary tools. (See more about FaSoC at `website `_)
+Contact: mehdi@umich.edu
* **Temperature sensor -**
.. image:: https://github.com/idea-fasoc/OpenFASOC/actions/workflows/tempSense_sky130hd.yml/badge.svg
diff --git a/docs/source/notebooks/glayout/glayout_opamp.ipynb b/docs/source/notebooks/glayout/glayout_opamp.ipynb
index 4858898c3..80ecdc342 100644
--- a/docs/source/notebooks/glayout/glayout_opamp.ipynb
+++ b/docs/source/notebooks/glayout/glayout_opamp.ipynb
@@ -571,9 +571,9 @@
},
"source": [
"### 3. PEX and Spice Simulation\n",
- "**Parasitic Extraction** (PEX) is the process of calculating the parasitic capacitive, resistive, and inductive effects of an electronic circuit layout to create an accurate analog model for simulation. In this Op-Amp example, parasitic extraction is done using the open-source tool [Magic](http://opencircuitdesign.com/magic/) and a SPICE netlist is generated for simulation using [this](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/extract.bash.template) script.\n",
+ "**Parasitic Extraction** (PEX) is the process of calculating the parasitic capacitive, resistive, and inductive effects of an electronic circuit layout to create an accurate analog model for simulation. In this Op-Amp example, parasitic extraction is done using the open-source tool [Magic](http://opencircuitdesign.com/magic/) and a SPICE netlist is generated for simulation using [this](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/glayout/tapeout/tapeout_and_RL/extract.bash.template) script.\n",
"\n",
- "[This](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/gdsfactory-gen/tapeout_and_RL/opamp_perf_eval.sp) SPICE testbench is used to run simulations on the post-PEX Op-Amp netlist. The testbench calculates the DC Gain, Unity Gain Bandwidth (UGB), Phase Margin, and 3dB Bandwidth at different values of the bias currents within a range. The best values of these metrics and the values of bias current at which they are achieved are written to `result_ac.txt`. The average total power consumption, and the estimated power consumption for the first two stages of the Op-Amp is written to `result_power.txt`.\n",
+ "[This](https://github.com/idea-fasoc/OpenFASOC/blob/main/openfasoc/generators/glayout/tapeout/tapeout_and_RL/opamp_perf_eval.sp) SPICE testbench is used to run simulations on the post-PEX Op-Amp netlist. The testbench calculates the DC Gain, Unity Gain Bandwidth (UGB), Phase Margin, and 3dB Bandwidth at different values of the bias currents within a range. The best values of these metrics and the values of bias current at which they are achieved are written to `result_ac.txt`. The average total power consumption, and the estimated power consumption for the first two stages of the Op-Amp is written to `result_power.txt`.\n",
"\n",
"The `brute_force_full_layout_and_PEXsim()` function generates the full layouts for a given list of parameters, runs the post-PEX simulations, and returns an array with the simulation results corresponding to each set of parameters.\n",
"\n",
diff --git a/openfasoc/generators/glayout/glayout/flow/testbench/README.md b/openfasoc/generators/glayout/glayout/flow/testbench/README.md
new file mode 100644
index 000000000..17bd91d9b
--- /dev/null
+++ b/openfasoc/generators/glayout/glayout/flow/testbench/README.md
@@ -0,0 +1,51 @@
+# NGSpice Testbenches
+
+This directory contains testbenches to evaluate components generated from the glayout flow using NGSpice.
+
+Currently, the following testbenches are available:
+
+- `diffpair_tb.sp`: Testbench for the differential pair
+- `opamp_tb.sp` : Testbench for a 2 stage Operational Amplifier
+- `currmirror_tb.sp` : Testbench for the current mirror
+
+To run the testbenches, you need to have NGSpice installed and the python script `process_tb.py` must be run
+
+It can be run as follows -
+
+```
+python3 process_tb.py \
+--temperature \
+--mode \
+--pdkroot \
+--testbench \
+--pexpath \
+--modulename
+```
+
+The first four arguments are not mandatory. Their default values are as follows -
+
+- `temperature` : 27
+- `mode` : stp
+- `pdkroot` : /usr/bin/miniconda3/share/pdk/
+- `testbench` : opamp
+
+
+**The placeholders in the testbenches with `@@__` can be filled in manually as well, if the user so choses. Take care of the following if you use the script:**
+1. Temperature must be an integer
+ - Temperature less than 0 is automatically treated as a cryo sim if the python script is used
+ - Temperature equal to 27 degrees is treated as STP
+2. The PDK_ROOT must be a valid and accessible path
+3. The pex script path (can be a post or pre-pex netlist) must exist
+4. The module name must be the exact same as in the netlist
+ - Pin orders:
+ - Differential Pair
+ `XDUT minus drain_right drain_left source plus @@MODULE_NAME`
+ - Current Mirror
+ `XDUT mirr_drain ref_drain GND @@MODULE_NAME`
+ - Opamp
+ `XDUT GND csoutputnetNC vo VDD vip vin biascsn biason biasdpn @@MODULE_NAME`
+ - `csoutputnetNC` is the 2nd stage Amplifier's output
+ - `vo` is the output from the NMOS driver circuit
+ - the current bias components are connected to the mirror drains of the corresponding current mirrors
+
+***The results are written to the directory that the script is run in.***
diff --git a/openfasoc/generators/glayout/glayout/flow/testbench/currmirror_tb.sp b/openfasoc/generators/glayout/glayout/flow/testbench/currmirror_tb.sp
new file mode 100644
index 000000000..d77225b03
--- /dev/null
+++ b/openfasoc/generators/glayout/glayout/flow/testbench/currmirror_tb.sp
@@ -0,0 +1,106 @@
+* currmirr_perf_eval.sp
+** OpenFASOC Team, Chetanya Goyal 2024, As a part of GSoC
+.temp 25
+
+.param bref = 5u
+.param rbias = 10k
+.param cbias = 1p
+* input voltages
+Vsup VDD GND 1.8
+
+* measure current through ref_drain and mirr_drain
+V1 ref_drain GND DC 0
+V2 mirr_drain GND DC 0
+
+* source bias
+Iref VDD ref_drain {bref}
+
+* bias resistors
+R1 ref_drain VDD {rbias}
+
+.lib /usr/bin/miniconda3/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice tt
+.include @@PEX_PATH
+XDUT mirr_drain ref_drain GND @@MODULE_NAME
+* .ac dec 10 10 10G
+.control
+
+echo "Starting simulation"
+set filetype = ascii
+let maxBiasRef = -1
+let minCurrDiff = 987654321
+let maxBiasR = -1
+
+* init biases
+let linear_step_until = 0u
+let linear_step_default = 1.1u
+let bias_ref_Min = 0.5u
+let bias_ref_Max = 50u
+let bias_ref_logStep = 1.1
+let bias_rbias_Min = 1k
+let bias_rbias_Max = 1Meg
+let bias_rbias_logStep = 1.1
+
+let bias_ref = bias_ref_Min
+let bias_rbias = bias_rbias_Min
+
+let index = 0
+
+while bias_ref le bias_ref_Max
+ while bias_rbias le bias_rbias_Max
+
+ * this way because matching is necessary
+ * place
+ alter R1 = $&bias_rbias
+ alter iref = $&bias_ref
+
+ echo "~~~~ Run #$&index ~~~~"
+ echo "Bias Current: $&bias_ref"
+ echo "Bias Resistor: $&bias_rbias"
+
+ op
+ save mirr_drain ref_drain
+ let mirr_curr = bias_ref
+ let ref_curr = (1.8 - v(ref_drain))/bias_rbias
+ let currdiff = (( abs( abs(mirr_curr) - abs(ref_curr) ) ) / abs(mirr_curr)) * 100
+
+ echo "mirr_curr = $&mirr_curr"
+ echo "ref_curr = $&ref_curr"
+ echo "currdiff = $&currdiff %"
+
+ * update max values
+ if ( currdiff le minCurrDiff )
+ let minCurrDiff = currdiff
+ let maxBiasRef = bias_ref
+ let maxBiasR = bias_rbias
+ end
+ let index = index + 1
+ let bias_rbias = bias_rbias * bias_rbias_logStep
+ end
+ let bias_rbias = bias_rbias_Min
+ if ( linear_step_until ge bias_ref )
+ let bias_ref = bias_ref + linear_step_default
+ else
+ let bias_ref = bias_ref * bias_ref_logStep
+ end
+end
+
+echo "Simulation complete"
+echo "Best Bias Current: $&maxBiasRef"
+echo "Min Curr Diff: $&minCurrDiff %"
+echo "Best Bias Resistance: $&maxBiasR"
+
+wrdata result_ac.txt maxBiasRef minCurrDiff maxBiasR
+alterparam bref = $&maxBiasRef
+alterparam rbias = $&maxBiasR
+reset
+
+op
+let ptotal_exact = i(Vsup) * 1.8
+wrdata result_power.txt ptotal_exact
+echo "Best power usage: $&ptotal_exact"
+
+
+.endc
+.GLOBAL VDD
+.GLOBAL GND
+.end
diff --git a/openfasoc/generators/glayout/glayout/flow/testbench/diffpair_tb.sp b/openfasoc/generators/glayout/glayout/flow/testbench/diffpair_tb.sp
new file mode 100644
index 000000000..811119abc
--- /dev/null
+++ b/openfasoc/generators/glayout/glayout/flow/testbench/diffpair_tb.sp
@@ -0,0 +1,160 @@
+* diffpair_perf_eval.sp
+** OpenFASOC Team, Chetanya Goyal 2024, As a part of GSoC
+.temp {@@TEMP}
+.param bdp = 5u
+.param r1p = 10k
+.param r2p = 10k
+.param vbias = 0.8
+
+* input voltages
+Vsup VDD GND 1.8
+Vbias net1 GND {Vbias}
+Vp plus net1 AC 0.5
+Vn minus net1 AC -0.5
+
+* source bias
+Ibiasdp source GND {bdp}
+
+* bias resistors
+R1 drain_left VDD {r1p}
+R2 drain_right VDD {r2p}
+
+.lib @@PDK_ROOT/sky130A/libs.tech/ngspice/sky130.lib.spice tt
+
+.include @@PEX_PATH
+XDUT minus drain_right drain_left source plus @@MODULE_NAME
+
+* .ac dec 10 10 10G
+.control
+
+echo "Starting simulation"
+set filetype = ascii
+let maxBiasDP = -1
+let maxVinP = -1
+let maxBiasR = -1
+let minVinN = 1
+let maxFOM = -1
+let maxDiffGain = -1
+let minCommonModeGain = 987654321
+let maxCMRR = -1
+let maxThreeDB = -1
+let minNoiseFig = -1
+
+* init biases
+let linear_step_until = 0u
+let linear_step_default = 1.1u
+let bias_dp_Min = 0.1u
+let bias_dp_Max = 5u
+let bias_dp_logStep = 1.8
+let bias_r2_Min = 0.85Meg
+let bias_r2_Max = 10G
+let bias_r2_logStep = 1.3
+let bias_voltage_min = 0.7
+let bias_voltage_max = 1.3
+let bias_voltage_step = 0.15
+
+let bias_dp = bias_dp_Min
+let bias_r1 = bias_r1_Min
+let bias_r2 = bias_r2_Min
+let bias_voltage = bias_voltage_min
+* let vinp_Min = 0.1
+* let vinp_Max = 1.0
+* let vinp_logStep = 1.2
+* let vinn_Min = -0.1
+* let vinn_Max = -1.0
+* let vinn_logStep = 1.2
+* let vinstep = 0.1
+* let vinp = vinp_Min
+* let vinn = vinn_Min
+
+let index = 0
+while bias_voltage le bias_voltage_max
+ while bias_dp le bias_dp_Max
+ while bias_r2 le bias_r2_Max
+
+ * this way because matching is necessary
+ alter R1 = $&bias_r2
+ alter ibiasdp = $&bias_dp
+ alter R2 = $&bias_r2
+ alter Vbias = $&bias_voltage
+
+ echo "~~~~ Run #$&index ~~~~"
+ echo "Bias Current DP: $&bias_dp"
+ echo "Bias Resistor R1: $&bias_r2"
+ echo "Bias Resistor R2: $&bias_r2"
+ echo "Bias Voltage: $&bias_voltage"
+
+ save drain_left drain_right
+ ac dec 10 10 1G
+ let vo = (v(drain_right) - v(drain_left))
+ let vadd = (v(drain_right))
+ meas ac diff_gain find vdb(vo) at=10
+ * meas ac common_mode_gain find vdb(vadd) at=10
+ alter Vn ac=0.5
+ meas ac common_mode_gain find vdb(vadd) at=10
+ alter Vn ac=-0.5
+ let threedbgain = diff_gain - 3
+ meas ac threedb when vd(vo) = threedbgain
+
+ * update max values
+ let FOM = diff_gain / bias_dp
+ if ( FOM ge maxFOM )
+ let maxFOM = FOM
+ let maxDiffGain = diff_gain
+ * let maxCommonModeGain = common_mode_gain
+ * let maxCMRR = cmrr
+ let maxThreeDB = threedb
+ let maxBiasDP = bias_dp
+ let maxBiasR = bias_r2
+ end
+ if ( common_mode_gain le minCommonModeGain )
+ if ( common_mode_gain ge 0 )
+ let minCommonModeGain = common_mode_gain
+ end
+ end
+ let index = index + 1
+ let bias_r2 = bias_r2 * bias_r2_logStep
+ end
+ let bias_r2 = bias_r2_Min
+ if ( linear_step_until ge bias_dp )
+ let bias_dp = bias_dp + linear_step_default
+ else
+ let bias_dp = bias_dp * bias_dp_logStep
+ end
+ end
+ let bias_dp = bias_dp_Min
+ let bias_voltage = bias_voltage + bias_voltage_step
+end
+let maxCMRR = maxDiffGain / minCommonModeGain
+echo "Max Bias DP: $&maxBiasDP"
+echo "Max Bias Resistance: $&maxBiasR"
+echo "Max FOM: $&maxFOM"
+echo "Max Diff Gain: $&maxDiffGain"
+echo "Min Common Mode Gain: $&minCommonModeGain"
+echo "Max CMRR: $&maxCMRR"
+echo "Max 3dB: $&maxThreeDB"
+echo "Max Bias R: $&maxBiasR"
+wrdata result_ac.txt maxBiasDP maxFOM maxDiffGain minCommonModeGain maxCMRR maxThreeDB maxBiasR
+
+alterparam bdp = $&maxBiasDP
+alterparam r1p = $&maxBiasR
+alterparam r2p = $&maxBiasR
+reset
+
+op
+let ptotal_exact = i(Vsup) * -1.8
+wrdata result_power.txt ptotal_exact
+echo "Power usage: $&ptotal_exact"
+
+reset
+noise V(drain_left) Vp dec 100 1k 10G
+setplot previous
+let integ = integ(onoise_spectrum)
+let total_noise = sqrt(integ[length(integ)-1])
+wrdata result_noise.txt total_noise
+echo "Total Noise: $&total_noise"
+
+.endc
+.GLOBAL VDD
+.GLOBAL GND
+.end
\ No newline at end of file
diff --git a/openfasoc/generators/glayout/glayout/flow/testbench/opamp_tb.sp b/openfasoc/generators/glayout/glayout/flow/testbench/opamp_tb.sp
new file mode 100644
index 000000000..c2ad59138
--- /dev/null
+++ b/openfasoc/generators/glayout/glayout/flow/testbench/opamp_tb.sp
@@ -0,0 +1,183 @@
+* opamp_perf_eval.sp
+** OpenFASOC Team, Ryan Wans 2023
+.param mc_mm_switch=0
+
+** IMPORTANT: Temperature setting is added automatically in the reading
+** of this file on line 6 as 25. DO NOT OVERRIDE.
+.temp {@@TEMP}
+
+*.save all
+** Define global parameters for altering
+.param bdp = 5u
+.param bcs = 5u
+.param bo = 5u
+
+** Define netlist
+Vsupply VDD GND 1.8
+Vindc net1 GND 1
+V2 vin net1 AC 0.5
+V3 vip net1 AC -0.5
+*.save i(vindc)
+*.save i(vsupply)
+*.save i(v2)
+*.save i(v3)
+
+* bias currents
+Ibiasdp VDD biasdpn {bdp}
+Ibiascs VDD biascsn {bcs}
+Ibiaso VDD biason {bo}
+
+** Import SKY130 libs (this should be replaced with a path relative to some env variable)
+* the ones with double * will not be used. The one with only 1 * will be used
+
+** example not used
+**@@stp .include /home/rw/work/open_pdks/sky130/sky130A/libs.ref/sky130_fd_sc_hvl/spice/sky130_fd_sc_hvl.spice
+
+** GCP machine
+.lib @@PDK_ROOT/sky130A/libs.tech/ngspice/sky130.lib.spice tt
+*@@stp .include @@PDK_ROOT/sky130A/libs.ref/sky130_fd_sc_hvl/spice/sky130_fd_sc_hvl.spice
+
+
+** Import cryo libs (these are stored in the sky130A folder)
+*@@cryo .include ./sky130A/cryo_models/nshort.spice
+*@@cryo .include ./sky130A/cryo_models/nshortlvth.spice
+*@@cryo .include ./sky130A/cryo_models/pmos.spice
+
+** Import opamp subcircuit
+.include @@PEX_PATH
+* + diffpairibias
+XDUT GND csoutputnetNC vo VDD vip vin biascsn biason biasdpn @@MODULE_NAME
+* parameter sweep
+** Run initial analysis
+*.save all
+*.options savecurrents
+*.ac dec 10 10 10G
+.control
+** Set initial values
+set filetype = ascii
+let maxFOM = -1
+let maxUGB = -1
+let maxBics = -1
+let maxBidp = -1
+let maxBio = -1
+let savedPhaseMargin = -1
+let savedDCGain = -1
+let savedthreedbBW = -1
+
+* dp and cs bias log step
+*let linear_step_until = 0u
+*let linear_step_default = 1.1u
+*let bias_dp_Min = 25u
+*let bias_dp_Max = 200u
+*let bias_dp_logStep = 1.15
+*let bias_cs_Min = 30u
+*let bias_cs_Max = 1m
+*let bias_cs_logStep = 1.18
+
+* dp and cs bias log step
+let linear_step_until = 0u
+let linear_step_default = 1.1u
+let bias_dp_Min = 1u
+let bias_dp_Max = 25u
+let bias_dp_logStep = 1.2
+let bias_cs_Min = 1u
+let bias_cs_Max = 25u
+let bias_cs_logStep = 1.2
+
+* output bias linear step
+let bias_o_Min = 93.5u
+let bias_o_Max = 94u
+let bias_o_Step = 2u
+
+let bias_dp = bias_dp_Min
+let bias_cs = bias_cs_Min
+let bias_o = bias_o_Min
+
+let absolute_counter = 0
+
+** Sweep bias voltages
+while bias_cs le bias_cs_Max
+ while bias_dp le bias_dp_Max
+ while bias_o le bias_o_Max
+ *reset
+ alter ibiascs = $&bias_cs
+ alter ibiasdp = $&bias_dp
+ alter ibiaso = $&bias_o
+ echo "-- Run # $&absolute_counter -- "
+ echo "CS: $&bias_cs"
+ echo "Diff: $&bias_dp"
+ echo "Out: $&bias_o"
+
+ save vo
+ ac dec 10 10 10G
+ ** Find unity-gain bw point
+ meas ac ugb_f when vdb(vo)=0
+ ** Measure phase margin
+ let phase = (180/PI)*vp(vo)
+ meas ac pm find phase when vdb(vo)=0
+ let pm_FOM_factor = pm > 45 ? 1 : 0.0000001
+ ** Measure DC(ish) gain
+ meas ac dcg find vdb(vo) at=10
+ ** Measure 3db BW
+ let threedbabsgain = dcg - 3
+ meas ac threedb when vdb(vo)=threedbabsgain FALL=1
+ ** if FOM is better than previous max save results
+ let FOM = pm_FOM_factor * ugb_f / (bias_cs + bias_dp)
+ if ( FOM ge maxFOM )
+ let maxFOM = FOM
+ let maxUGB = ugb_f
+ let maxBics = bias_cs
+ let maxBidp = bias_dp
+ let maxBio = bias_o
+ let savedPhaseMargin = pm % 360
+ let savedDCGain = dcg
+ let savedthreedbBW = threedb
+ end
+
+ let absolute_counter = absolute_counter + 1
+ let bias_o = bias_o + bias_o_Step
+ end
+ ** Reset biasCurrent_o for next value of biasCurrent_dp
+ let bias_o = bias_o_Min
+ if ( linear_step_until ge bias_dp )
+ let bias_dp = bias_dp + linear_step_default
+ else
+ let bias_dp = bias_dp * bias_dp_logStep
+ end
+ end
+ ** Reset biasCurrent_dp for next value of biasCurrent_cs
+ let bias_dp = bias_dp_Min
+ if ( linear_step_until ge bias_cs )
+ let bias_cs = bias_cs + linear_step_default
+ else
+ let bias_cs = bias_cs * bias_cs_logStep
+ end
+end
+** Export global maxima
+wrdata result_ac.txt maxUGB maxBidp maxBics maxBio savedPhaseMargin savedDCGain savedthreedbBW
+
+** Export power usage of correctly biased opamp
+alterparam bcs = $&maxBics
+alterparam bdp = $&maxBidp
+alterparam bo = $&maxBio
+reset
+
+op
+let estimated_output_1to1_ref = 336.6u
+let ptotal_exact = -i(vsupply)*1.8
+let estimated_two_stagepwr = ptotal_exact - estimated_output_1to1_ref
+wrdata result_power.txt ptotal_exact estimated_two_stagepwr
+
+** Run noise analysis on opamp w/ best gain
+reset
+noise V(vo) v2 dec 100 1k 10G
+setplot previous
+let integ = integ(onoise_spectrum)
+let totalNoise = sqrt(integ[length(integ)-1])
+wrdata result_noise.txt totalNoise
+
+quit
+.endc
+.GLOBAL GND
+.GLOBAL VDD
+.end
\ No newline at end of file
diff --git a/openfasoc/generators/glayout/glayout/flow/testbench/process_tb.py b/openfasoc/generators/glayout/glayout/flow/testbench/process_tb.py
new file mode 100644
index 000000000..d13de93c0
--- /dev/null
+++ b/openfasoc/generators/glayout/glayout/flow/testbench/process_tb.py
@@ -0,0 +1,144 @@
+import sys
+import os
+import argparse
+from pathlib import Path
+from typing import Union, Optional
+import tempfile
+import subprocess as sp
+
+def process_spice_testbench(
+ testbench: Union[str,Path],
+ temperature: Optional[int] = 25,
+ pex_path: Optional[str] = None,
+ module_name: Optional[str] = None,
+ mode: Optional[str] = 'normal'
+):
+ global PDK_ROOT
+ PDK_ROOT = Path(PDK_ROOT).resolve()
+ testbench = Path(testbench).resolve()
+ if not testbench.is_file():
+ raise ValueError("testbench must be file")
+ if not PDK_ROOT.is_dir():
+ raise ValueError("PDK_ROOT is not a valid directory")
+ PDK_ROOT = str(PDK_ROOT)
+ with open(testbench, "r") as spice_file:
+ spicetb = spice_file.read()
+ spicetb = spicetb.replace('{@@TEMP}', str(temperature))
+ spicetb = spicetb.replace("@@PDK_ROOT", PDK_ROOT)
+ spicetb = spicetb.replace("@@PEX_PATH", pex_path)
+ spicetb = spicetb.replace("@@MODULE_NAME", module_name)
+ if mode == "cryo":
+ spicetb = spicetb.replace("*@@cryo ","")
+ else:
+ spicetb = spicetb.replace("*@@stp ","")
+ with open(testbench, "w") as spice_file:
+ spice_file.write(spicetb)
+
+parser = argparse.ArgumentParser(
+ description='Process the testbench for the given design'
+)
+
+parser.add_argument(
+ '--temperature',
+ type = int,
+ help = 'Temperature to run simulation at in degrees',
+ default = 25
+)
+
+parser.add_argument(
+ '--mode',
+ type = str,
+ help = 'Temperature Mode: "stp", "cryo", "custom"',
+ default = 'stp'
+)
+
+parser.add_argument(
+ '--pdkroot',
+ type = str,
+ help = 'Path to the PDK_ROOT',
+ default = '/usr/bin/miniconda3/share/pdk/'
+)
+
+parser.add_argument(
+ '--testbench',
+ type = str,
+ help = 'The component to run: opamp, currmirror, diffpair',
+ default = 'opamp'
+)
+
+parser.add_argument(
+ '--pexpath',
+ type = str,
+ help = 'Path to the extracted spice netlist'
+)
+
+parser.add_argument(
+ '--modulename',
+ type = str,
+ help = 'Name of the module to simulate'
+)
+
+args = parser.parse_args()
+
+if __name__ == '__main__':
+ global PDK_ROOT
+
+ PDK_ROOT = args.pdkroot
+ if Path(PDK_ROOT).resolve().exists() == False:
+ print(f'PDK_ROOT path {PDK_ROOT} does not exist')
+ sys.exit(1)
+ else:
+ PDK_ROOT = Path(PDK_ROOT).resolve()
+
+ temp = args.temperature
+ mod = args.testbench
+ pex_path = str(Path(args.pexpath).resolve())
+
+ if mod == 'opamp':
+ testbench = Path('opamp_tb.sp').resolve()
+ elif mod == 'currmirror':
+ testbench = Path('currmirror_tb.sp').resolve()
+ elif mod == 'diffpair':
+ testbench = Path('diffpair_tb.sp').resolve()
+ else:
+ print(f'Invalid component {mod}! Exiting...')
+ sys.exit(1)
+
+ if args.mode == 'cryo':
+ print('Overriding temperature to -269 degreees for cryo sims!')
+ temp = -269
+ elif args.mode == 'stp':
+ print('Overriding temperature to 25 degreees for stp sims!')
+ temp = 25
+ elif args.mode == 'custom':
+ if temp < 0:
+ print('Running Sim in cryo mode! Pinning temperature at -269 degrees!')
+ elif temp == 25:
+ print('Running Sim in stp mode! Pinning temperature at 25 degrees!')
+ else:
+ print(f'Running Sim in custom mode! Pinning temperature at {temp} degrees!')
+
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ # copy testbench to temp directory
+ tmpdirname = Path(tmpdirname)
+ testbench = str(args.testbench) + '_tb.sp'
+ print(testbench)
+ target = tmpdirname / testbench
+ with open(target, 'w') as f:
+ with open(testbench, 'r') as tb:
+ f.write(tb.read())
+
+
+ process_spice_testbench(
+ testbench=target,
+ temperature=temp,
+ mode=args.mode,
+ pex_path=pex_path,
+ module_name=args.modulename
+ )
+
+ print(f'Processed testbench for {mod} at {temp} degrees in {args.mode} mode!')
+ print(f'Running simulation using: ngspice -b {testbench}')
+
+ sp.Popen(['ngspice', '-b', testbench], cwd=tmpdirname).wait()
+