| #!/usr/bin/env python3 |
| #------------------------------------------------------------------------------ |
| # |
| # run_spice_tests.py -- |
| # |
| # Run all ngspice tests, assuming an input file "devices.txt" containing, |
| # with one entry per line: |
| # |
| # <device_name> <device_type> <expected_value> |
| # |
| # where <device_type> is one of: mosfet, bipolar, capacitor, resistor, or diode. |
| # |
| #------------------------------------------------------------------------------ |
| |
| import re |
| import os |
| import sys |
| import subprocess |
| import multiprocessing |
| |
| from plot_mosfet_iv import make_mosfet_iv_plot |
| from plot_bipolar_iv import make_bipolar_iv_plot |
| from plot_bipolar_beta import make_bipolar_beta_plot |
| from plot_diode_iv import make_diode_iv_plot |
| |
| __dir__ = os.path.dirname(os.path.abspath(__file__)) |
| |
| def make_testdir(outdir): |
| testdir = os.path.join(outdir, 'tests') |
| if not os.path.exists(testdir): |
| os.makedirs(testdir) |
| return testdir |
| |
| |
| def getmake(outdir): |
| testdir = make_testdir(outdir) |
| |
| testrunner = os.path.join(testdir, 'Makefile') |
| # FIXME: Write out a Makefile |
| with open(testrunner, 'w') as ofile: |
| # FIXME: run ngspice |
| |
| # FIXME: run the check_XXXX.py script on the ngspice output |
| |
| # FIXME: run the plot_XXX.py script on the ngspice output |
| |
| |
| #------------------------------------------------------------------------------ |
| # Read ".spice.in" file, replace DEVICENAME string with the device name, |
| # PDKVERSION with the version (v0.20.1), and CORNER with the simulation |
| # corner (tt), and write out as ".spice" file in "results" directory. |
| #------------------------------------------------------------------------------ |
| |
| def genspice(outdir, scriptname, devicename, version, corner): |
| print('Generating simulation netlist for device ' + devicename) |
| scripttype = scriptname.split('_', 1)[1] |
| scriptpath = os.path.join(__dir__, scriptname+'.in') |
| with open(scriptpath, 'r') as ifile: |
| spicelines = ifile.read().splitlines() |
| |
| outlines = ['* '+devicename+' Spice Simulation Test'] |
| for line in spicelines: |
| newline = line.replace('DEVICENAME', devicename) |
| newline = newline.replace('PDKVERSION', version) |
| newline = newline.replace('CORNER', corner) |
| outlines.append(newline) |
| |
| testdir = make_testdir(outdir) |
| |
| testfile = os.path.join(testdir, devicename + '_' + scripttype) |
| with open(testfile, 'w') as ofile: |
| for line in outlines: |
| print(line, file=ofile) |
| |
| return testfile |
| |
| #------------------------------------------------------------------------------ |
| # Run a spice simulation (or two) for the device specified in 'line' (obtained |
| # from the list of devices and expected values). |
| #------------------------------------------------------------------------------ |
| |
| def do_for_device(outdir, devicename, version, corner): |
| if 'ind' in devicename: |
| # Inductors not handled (yet)! |
| pass |
| elif 'cap_mim_' in devicename: |
| devicetype = 'mimcap' |
| elif 'cap_vpp_' in devicename: |
| devicetype = 'capacitor' |
| elif 'esd_rf_diode' in devicename: |
| devicetype = 'diode_dev' |
| elif 'diode_' in devicename: |
| devicetype = 'diode' |
| elif 'nfet_' in devicename: |
| devicetype = 'nfet' |
| elif 'pfet_' in devicename: |
| devicetype = 'pfet' |
| elif 'pnp_' in devicename: |
| devicetype = 'pnp' |
| elif 'npn_' in devicename: |
| devicetype = 'npn' |
| elif 'res_' in devicename: |
| devicetype = 'resistor' |
| else: |
| print('Unknown device type for device name ' + devicename + '. Cannot simulate.') |
| return -1 |
| |
| print('Diagnostic: Determined device ' + devicename + ' to be type ' + devicetype) |
| |
| if devicetype in ('nfet', 'pfet'): |
| genspice(outdir, devicetype + '_iv.spice', devicename, version, corner) |
| genspice(outdir, devicetype + '_vth.spice', devicename, version, corner) |
| genmake( |
| outdir, |
| { |
| devicetype + '_iv.spice' : 'plot_mostfet_iv', |
| devicetype + '_vth.spice': '', |
| }, |
| ) |
| |
| elif devicetype in ('npn', 'pnp'): |
| genspice(outdir, devicetype + '_iv.spice', devicename, version, corner) |
| genspice(outdir, devicetype + '_beta.spice', devicename, version, corner) |
| genmake( |
| outdir, |
| { |
| devicetype + '_iv.spice' : 'plot_bipolar_iv', |
| devicetype + '_beta.spice': 'plot_bipolar_beta', |
| }, |
| ) |
| |
| elif devicetype == 'mimcap': |
| genspice(outdir, 'cap_mim_value.spice', devicename, version, corner) |
| genmake( |
| outdir, |
| { |
| 'cap_mim_value.spice': None, |
| }, |
| ) |
| |
| elif devicetype == 'capacitor': |
| genspice(outdir, 'cap_vpp_value.spice', devicename, version, corner) |
| genmake( |
| outdir, |
| { |
| 'cap_vpp_value.spice': None, |
| }, |
| ) |
| |
| elif devicetype == 'resistor': |
| genspice(outdir, 'res_value.spice', devicename, version, corner) |
| genmake( |
| outdir, |
| { |
| 'res_value.spice': None, |
| }, |
| ) |
| |
| elif devicetype in ('diode', 'diode_dev'): |
| genspice(outdir, devicetype + '_vth.spice', devicename, version, corner) |
| genmake( |
| outdir, |
| { |
| devicetype + '_iv.spice': 'plot_diode_iv', |
| }, |
| ) |
| |
| |
| #------------------------------------------------------------------------------ |
| # Main script starts here (no arguments, at least for now) |
| #------------------------------------------------------------------------------ |
| def main(d): |
| # To do: Loop through version and corner. For now, fixed. |
| version = 'v0.20.1' |
| corner = 'tt' |
| |
| device = os.path.basename(d) |
| |
| dp = os.path.abspath(d) |
| if not os.path.isdir(dp): |
| print("Skipping:", dp) |
| return 0 |
| do_for_device(dp,device,version,corner) |
| return 0 |
| for c in sorted(os.listdir(dp)): |
| if not os.path.isdir(c): |
| print("Skipping:", c) |
| continue |
| |
| cp = os.path.join(dp, c) |
| |
| |
| if __name__ == "__main__": |
| for a in sys.argv[1:]: |
| r = main(a) |
| if r != 0: |
| sys.exit(r) |
| sys.exit(0) |