| #!/usr/bin/env python3 |
| # |
| # |
| import os |
| import re |
| import sys |
| import subprocess |
| |
| #--------------------------------------------------------------------------- |
| # usage: run_all.py [-nosim] [-keep] |
| # |
| # Run ngspice simulations on all major transistor devices in the process |
| # (excluding high-voltage > 3V devices) at all corners, and generate |
| # IRSIM parameter files for each corner. |
| # |
| # The "-nosim" option assumes that simulation output files have been |
| # saved, and will run the parser to generate the parameterf files from |
| # the existing ngspice output files. |
| # |
| # The "-keep" option will retain the ngspice input and output files |
| # after each parameter file has been generated. Otherwise, they will |
| # be removed. |
| #--------------------------------------------------------------------------- |
| |
| #--------------------------------------------------------------------------- |
| # Devices must be paired P and N for each test. There are some redundant |
| # devices below where one type exists that does not have a corresponding |
| # device in the opposite type. In cases of redundancy, the first results |
| # computed will be the ones used for that device. The third item in each |
| # list is the voltage range to use for the device, and determines which |
| # voltages are used for max/min/typ simulations. |
| #--------------------------------------------------------------------------- |
| |
| #--------------------------------------------------------------------------- |
| # NOTE: This method requires IRSIM 9.7.114, which supports multiple |
| # transistor device parameters and multiple supply voltages. |
| #--------------------------------------------------------------------------- |
| |
| #--------------------------------------------------------------------------- |
| # To do: Speed this up by using multiprocessing |
| #--------------------------------------------------------------------------- |
| |
| # Parse options |
| |
| keep = False |
| nosim = False |
| |
| options = [] |
| arguments = [] |
| for item in sys.argv[1:]: |
| if item.find('-', 0) == 0: |
| options.append(item) |
| else: |
| arguments.append(item) |
| |
| if len(arguments) > 0: |
| print("Usage: run_all.py [-nosim] [-keep]") |
| sys.exit(1) |
| |
| if '-keep' in options: |
| keep = True |
| print("Keep mode: Retaining all intermediate files.") |
| |
| if '-nosim' in options: |
| nosim = True |
| print("No-sim mode: Not running any simulations.") |
| |
| if '-help' in options: |
| print("Usage: run_all.py [-nosim] [-keep]") |
| sys.exit(0) |
| |
| #--------------------------------------------------------------------------- |
| # Each entry in "devices" list has 9 items: |
| # [test-text, pFET-name, nFET-name, voltage-range, |
| # p-length, p-width, n-length, n-width, load-cap] |
| #--------------------------------------------------------------------------- |
| |
| devices = [ |
| [ |
| "1.8V devices", |
| "sky130_fd_pr__pfet_01v8", "sky130_fd_pr__nfet_01v8", |
| "1v8", 0.15, 1.0, 0.15, 1.0, 250 |
| ], |
| [ |
| "1.8V LVT devices", |
| "sky130_fd_pr__pfet_01v8_lvt", "sky130_fd_pr__nfet_01v8_lvt", |
| "1v8", 0.35, 1.0, 0.15, 1.0, 250 |
| ], |
| [ |
| "1.8V HVT pFET", |
| "sky130_fd_pr__pfet_01v8_hvt", "sky130_fd_pr__nfet_01v8", |
| "1v8", 0.45, 1.0, 0.15, 1.0, 250 |
| ], |
| [ |
| "SRAM latching FETs", |
| "sky130_fd_pr__special_pfet_pass", "sky130_fd_pr__special_nfet_latch", |
| "1v8", 0.15, 0.14, 0.15, 0.21, 100 |
| ], |
| [ |
| "SRAM pass nFET", |
| "sky130_fd_pr__special_pfet_pass", "sky130_fd_pr__special_nfet_pass", |
| "1v8", 0.15, 0.14, 0.15, 0.14, 100 |
| ], |
| [ |
| "3.3V devices", |
| "sky130_fd_pr__pfet_g5v0d10v5", "sky130_fd_pr__nfet_g5v0d10v5", |
| "3v3", 0.50, 1.0, 0.50, 1.0, 250 |
| ], |
| [ |
| "5.0V native nFET", |
| "sky130_fd_pr__pfet_g5v0d10v5", "sky130_fd_pr__nfet_05v0_nvt", |
| "3v3", 0.50, 1.0, 0.90, 1.0, 250 |
| ] |
| ] |
| |
| voltages1v8 = [ 1.62, 1.80, 1.98 ] |
| |
| voltages3v3 = [ 2.97, 3.30, 3.63 ] |
| |
| vnames = [ 'low', 'nom', 'high' ] |
| |
| temps = [ -40, 27, 125 ] |
| |
| corners = [ "ss", "tt", "ff" ] |
| |
| # Read the parameter file header and save the contents |
| |
| header = [] |
| with open('header.txt', 'r') as ifile: |
| hlines = ifile.read().splitlines() |
| |
| for corner in corners: |
| for temp in temps: |
| tname = str(temp).replace('-', 'n') |
| for vidx in range(0,3): |
| vname = vnames[vidx] |
| |
| generated_files = [] |
| ndevtypes = [] |
| pdevtypes = [] |
| |
| ndynh = {} |
| ndynl = {} |
| pdynh = {} |
| pdynl = {} |
| nstat = {} |
| pstat = {} |
| |
| for devidx in range(0, len(devices)): |
| devicepair = devices[devidx] |
| devset = devicepair[0] |
| if len(devicepair) != 9: |
| print('Error: Bad entry for device set ' + devset + '.\n') |
| continue |
| ptype = devicepair[1] |
| ntype = devicepair[2] |
| vtype = devicepair[3] |
| plength = devicepair[4] |
| pwidth = devicepair[5] |
| nlength = devicepair[6] |
| nwidth = devicepair[7] |
| loadcap = devicepair[8] |
| |
| if ntype not in ndevtypes: |
| ndevtypes.append(ntype) |
| if ptype not in pdevtypes: |
| pdevtypes.append(ptype) |
| |
| if vtype == '1v8': |
| volt = voltages1v8[vidx] |
| else: |
| volt = voltages3v3[vidx] |
| |
| if not nosim: |
| |
| # Read template and generate SPICE simulation netlist |
| newlines = [] |
| with open('circuit_template.spi', 'r') as ifile: |
| template = ifile.read().splitlines() |
| for line in template: |
| outline = re.sub('CORNER', corner, line) |
| outline = re.sub('FULL_VOLTAGE', str(volt), outline) |
| outline = re.sub('HALF_VOLTAGE', str(volt / 2.0), outline) |
| outline = re.sub('TEMPERATURE', str(temp), outline) |
| outline = re.sub('DEVICENAME_N', ntype, outline) |
| outline = re.sub('DEVICENAME_P', ptype, outline) |
| outline = re.sub('WIDTH_N', str(nwidth), outline) |
| outline = re.sub('WIDTH_P', str(pwidth), outline) |
| outline = re.sub('LENGTH_N', str(nlength), outline) |
| outline = re.sub('LENGTH_P', str(plength), outline) |
| outline = re.sub('LOADCAP', str(loadcap), outline) |
| newlines.append(outline) |
| |
| simname = 'sky130_' + corner + '_' + vname + '_' + tname + '_devpair' + str(devidx) + '.spice' |
| |
| with open(simname, 'w') as ofile: |
| for line in newlines: |
| print(line, file=ofile) |
| |
| generated_files.append(simname) |
| |
| # Run ngspice simulation |
| |
| print('** Running simulation on ' + devset + '(file ' + simname + ')') |
| print('** Conditions: temp=' + tname + ' corner=' + corner |
| + ' volt=' + vname) |
| p = subprocess.run(['ngspice', simname], |
| stdout = subprocess.PIPE, |
| universal_newlines = True) |
| |
| if p.stdout: |
| parameters = p.stdout.splitlines() |
| |
| for parameter in parameters: |
| valueline = parameter.split() |
| if len(valueline) < 3: |
| continue |
| if valueline[0] == 'ndynh': |
| try: |
| ndynh[ntype] |
| except: |
| ndynh[ntype] = valueline[2] |
| elif valueline[0] == 'pdynh': |
| try: |
| pdynh[ptype] |
| except: |
| pdynh[ptype] = valueline[2] |
| elif valueline[0] == 'ndynl': |
| try: |
| ndynl[ntype] |
| except: |
| ndynl[ntype] = valueline[2] |
| elif valueline[0] == 'pdynl': |
| try: |
| pdynl[ptype] |
| except: |
| pdynl[ptype] = valueline[2] |
| elif valueline[0] == 'nstat': |
| try: |
| nstat[ntype] |
| except: |
| nstat[ntype] = valueline[2] |
| elif valueline[0] == 'pstat': |
| try: |
| pstat[ptype] |
| except: |
| pstat[ptype] = valueline[2] |
| else: |
| print('** No file ' + outname + '; skipping.') |
| |
| paramfile = 'sky130_' + corner + '_' + vname + '_' + tname + '.prm' |
| |
| with open(paramfile, 'w') as ofile: |
| for line in hlines: |
| print(line, file=ofile) |
| |
| # Now output information for every device |
| print('', file=ofile) |
| |
| for device in ndevtypes: |
| devicepair = next(item for item in devices if item[2] == device) |
| if not devicepair: |
| print('Error: Bad entry for nFET device ' + device + '.\n') |
| continue |
| devset = devicepair[0] |
| if len(devicepair) != 9: |
| print('Error: Bad entry for device set ' + devset + '.\n') |
| continue |
| ntype = devicepair[2] |
| vtype = devicepair[3] |
| nlength = devicepair[6] |
| nwidth = devicepair[7] |
| loadcap = devicepair[8] |
| |
| print('; C=' + str(loadcap) + ', N(w=' + str(nwidth) + ', l=' + str(nlength) + ')', file=ofile) |
| print('resistance ' + ntype + ' dynamic-high ' + str(nwidth) + ' ' + str(nlength) + ' ' + ndynh[ntype], file=ofile) |
| print('resistance ' + ntype + ' dynamic-low ' + str(nwidth) + ' ' + str(nlength) + ' ' + ndynl[ntype], file=ofile) |
| print('resistance ' + ntype + ' static ' + str(nwidth) + ' ' + str(nlength) + ' ' + nstat[ntype], file=ofile) |
| print('', file=ofile) |
| |
| for device in pdevtypes: |
| devicepair = next(item for item in devices if item[1] == device) |
| if not devicepair: |
| print('Error: Bad entry for pFET device ' + device + '.\n') |
| continue |
| devset = devicepair[0] |
| if len(devicepair) != 9: |
| print('Error: Bad entry for device set ' + devset + '.\n') |
| continue |
| ptype = devicepair[1] |
| vtype = devicepair[3] |
| plength = devicepair[4] |
| pwidth = devicepair[5] |
| loadcap = devicepair[8] |
| |
| print('; C=' + str(loadcap) + ', P(w=' + str(pwidth) + ', l=' + str(plength) + ')', file=ofile) |
| print('resistance ' + ptype + ' dynamic-high ' + str(pwidth) + ' ' + str(plength) + ' ' + pdynh[ptype], file=ofile) |
| print('resistance ' + ptype + ' dynamic-low ' + str(pwidth) + ' ' + str(plength) + ' ' + pdynl[ptype], file=ofile) |
| print('resistance ' + ptype + ' static ' + str(pwidth) + ' ' + str(plength) + ' ' + pstat[ptype], file=ofile) |
| print('', file=ofile) |
| |
| if not keep: |
| print('**Removing generated intermediate files.') |
| for file in generated_files: |
| try: |
| os.remove(file) |
| except: |
| pass |
| |
| sys.exit(0) |