From 64a112aa6f2a2c96d5a3651ccea5ac23a6a0fdb0 Mon Sep 17 00:00:00 2001 From: chetanyagoyal Date: Wed, 24 Jul 2024 15:27:39 +0000 Subject: [PATCH 1/2] first commit for testbenches --- .../glayout/glayout/flow/testbench/README.md | 35 ++++ .../glayout/flow/testbench/currmirror_tb.sp | 106 ++++++++++ .../glayout/flow/testbench/diffpair_tb.sp | 160 +++++++++++++++ .../glayout/flow/testbench/opamp_tb.sp | 183 ++++++++++++++++++ .../glayout/flow/testbench/process_tb.py | 144 ++++++++++++++ 5 files changed, 628 insertions(+) create mode 100644 openfasoc/generators/glayout/glayout/flow/testbench/README.md create mode 100644 openfasoc/generators/glayout/glayout/flow/testbench/currmirror_tb.sp create mode 100644 openfasoc/generators/glayout/glayout/flow/testbench/diffpair_tb.sp create mode 100644 openfasoc/generators/glayout/glayout/flow/testbench/opamp_tb.sp create mode 100644 openfasoc/generators/glayout/glayout/flow/testbench/process_tb.py 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..6d975ce45 --- /dev/null +++ b/openfasoc/generators/glayout/glayout/flow/testbench/README.md @@ -0,0 +1,35 @@ +# 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 + + +Do not modify the `@@___` values in the testbenches, these are automatically filled in by the python script + +The results are written to the directory that the script is run in. \ No newline at end of file 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() + From 333547ae2ef1d292956dcb2b24480318cd739d91 Mon Sep 17 00:00:00 2001 From: Chetanya Goyal <95761876+chetanyagoyal@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:31:15 +0530 Subject: [PATCH 2/2] Update README.md --- .../glayout/glayout/flow/testbench/README.md | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/openfasoc/generators/glayout/glayout/flow/testbench/README.md b/openfasoc/generators/glayout/glayout/flow/testbench/README.md index 6d975ce45..17bd91d9b 100644 --- a/openfasoc/generators/glayout/glayout/flow/testbench/README.md +++ b/openfasoc/generators/glayout/glayout/flow/testbench/README.md @@ -30,6 +30,22 @@ The first four arguments are not mandatory. Their default values are as follows - `testbench` : opamp -Do not modify the `@@___` values in the testbenches, these are automatically filled in by the python script - -The results are written to the directory that the script is run in. \ No newline at end of file +**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.***