Tim Edwards | d7c3a52 | 2022-10-01 17:58:29 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # |
| 4 | import os |
| 5 | import re |
| 6 | import sys |
| 7 | import subprocess |
| 8 | |
| 9 | #--------------------------------------------------------------------------- |
| 10 | # usage: run_all.py [-nosim] [-keep] |
| 11 | # |
| 12 | # Run ngspice simulations on all major transistor devices in the process |
| 13 | # (excluding high-voltage > 3V devices) at all corners, and generate |
| 14 | # IRSIM parameter files for each corner. |
| 15 | # |
| 16 | # The "-nosim" option assumes that simulation output files have been |
| 17 | # saved, and will run the parser to generate the parameterf files from |
| 18 | # the existing ngspice output files. |
| 19 | # |
| 20 | # The "-keep" option will retain the ngspice input and output files |
| 21 | # after each parameter file has been generated. Otherwise, they will |
| 22 | # be removed. |
| 23 | #--------------------------------------------------------------------------- |
| 24 | |
| 25 | #--------------------------------------------------------------------------- |
| 26 | # Devices must be paired P and N for each test. There are some redundant |
| 27 | # devices below where one type exists that does not have a corresponding |
| 28 | # device in the opposite type. In cases of redundancy, the first results |
| 29 | # computed will be the ones used for that device. The third item in each |
| 30 | # list is the voltage range to use for the device, and determines which |
| 31 | # voltages are used for max/min/typ simulations. |
| 32 | #--------------------------------------------------------------------------- |
| 33 | |
| 34 | #--------------------------------------------------------------------------- |
| 35 | # NOTE: This method requires IRSIM 9.7.114, which supports multiple |
| 36 | # transistor device parameters and multiple supply voltages. |
| 37 | #--------------------------------------------------------------------------- |
| 38 | |
| 39 | #--------------------------------------------------------------------------- |
| 40 | # To do: Speed this up by using multiprocessing |
| 41 | #--------------------------------------------------------------------------- |
| 42 | |
| 43 | # Parse options |
| 44 | |
| 45 | keep = False |
| 46 | nosim = False |
| 47 | |
| 48 | options = [] |
| 49 | arguments = [] |
| 50 | for item in sys.argv[1:]: |
| 51 | if item.find('-', 0) == 0: |
| 52 | options.append(item) |
| 53 | else: |
| 54 | arguments.append(item) |
| 55 | |
| 56 | if len(arguments) > 0: |
| 57 | print("Usage: run_all.py [-nosim] [-keep]") |
| 58 | sys.exit(1) |
| 59 | |
| 60 | if '-keep' in options: |
| 61 | keep = True |
| 62 | print("Keep mode: Retaining all intermediate files.") |
| 63 | |
| 64 | if '-nosim' in options: |
| 65 | nosim = True |
| 66 | print("No-sim mode: Not running any simulations.") |
| 67 | |
| 68 | if '-help' in options: |
| 69 | print("Usage: run_all.py [-nosim] [-keep]") |
| 70 | sys.exit(0) |
| 71 | |
| 72 | #--------------------------------------------------------------------------- |
| 73 | # Each entry in "devices" list has 9 items: |
| 74 | # [test-text, pFET-name, nFET-name, voltage-range, |
| 75 | # p-length, p-width, n-length, n-width, load-cap] |
| 76 | #--------------------------------------------------------------------------- |
| 77 | |
| 78 | devices = [ |
| 79 | [ |
| 80 | "1.8V devices", |
| 81 | "sky130_fd_pr__pfet_01v8", "sky130_fd_pr__nfet_01v8", |
| 82 | "1v8", 0.15, 1.0, 0.15, 1.0, 250 |
| 83 | ], |
| 84 | [ |
| 85 | "1.8V LVT devices", |
| 86 | "sky130_fd_pr__pfet_01v8_lvt", "sky130_fd_pr__nfet_01v8_lvt", |
| 87 | "1v8", 0.35, 1.0, 0.15, 1.0, 250 |
| 88 | ], |
| 89 | [ |
| 90 | "1.8V HVT pFET", |
| 91 | "sky130_fd_pr__pfet_01v8_hvt", "sky130_fd_pr__nfet_01v8", |
| 92 | "1v8", 0.45, 1.0, 0.15, 1.0, 250 |
| 93 | ], |
| 94 | [ |
| 95 | "SRAM latching FETs", |
| 96 | "sky130_fd_pr__special_pfet_pass", "sky130_fd_pr__special_nfet_latch", |
| 97 | "1v8", 0.15, 0.14, 0.15, 0.21, 100 |
| 98 | ], |
| 99 | [ |
| 100 | "SRAM pass nFET", |
| 101 | "sky130_fd_pr__special_pfet_pass", "sky130_fd_pr__special_nfet_pass", |
| 102 | "1v8", 0.15, 0.14, 0.15, 0.14, 100 |
| 103 | ], |
| 104 | [ |
| 105 | "3.3V devices", |
| 106 | "sky130_fd_pr__pfet_g5v0d10v5", "sky130_fd_pr__nfet_g5v0d10v5", |
| 107 | "3v3", 0.50, 1.0, 0.50, 1.0, 250 |
| 108 | ], |
| 109 | [ |
| 110 | "5.0V native nFET", |
| 111 | "sky130_fd_pr__pfet_g5v0d10v5", "sky130_fd_pr__nfet_05v0_nvt", |
| 112 | "3v3", 0.50, 1.0, 0.90, 1.0, 250 |
| 113 | ] |
| 114 | ] |
| 115 | |
| 116 | voltages1v8 = [ 1.62, 1.80, 1.98 ] |
| 117 | |
| 118 | voltages3v3 = [ 2.97, 3.30, 3.63 ] |
| 119 | |
| 120 | vnames = [ 'low', 'nom', 'high' ] |
| 121 | |
| 122 | temps = [ -40, 27, 125 ] |
| 123 | |
| 124 | corners = [ "ss", "tt", "ff" ] |
| 125 | |
| 126 | # Read the parameter file header and save the contents |
| 127 | |
| 128 | header = [] |
| 129 | with open('header.txt', 'r') as ifile: |
| 130 | hlines = ifile.read().splitlines() |
| 131 | |
| 132 | for corner in corners: |
| 133 | for temp in temps: |
| 134 | tname = str(temp).replace('-', 'n') |
| 135 | for vidx in range(0,3): |
| 136 | vname = vnames[vidx] |
| 137 | |
| 138 | generated_files = [] |
| 139 | ndevtypes = [] |
| 140 | pdevtypes = [] |
| 141 | |
| 142 | ndynh = {} |
| 143 | ndynl = {} |
| 144 | pdynh = {} |
| 145 | pdynl = {} |
| 146 | nstat = {} |
| 147 | pstat = {} |
| 148 | |
| 149 | for devidx in range(0, len(devices)): |
| 150 | devicepair = devices[devidx] |
| 151 | devset = devicepair[0] |
| 152 | if len(devicepair) != 9: |
| 153 | print('Error: Bad entry for device set ' + devset + '.\n') |
| 154 | continue |
| 155 | ptype = devicepair[1] |
| 156 | ntype = devicepair[2] |
| 157 | vtype = devicepair[3] |
| 158 | plength = devicepair[4] |
| 159 | pwidth = devicepair[5] |
| 160 | nlength = devicepair[6] |
| 161 | nwidth = devicepair[7] |
| 162 | loadcap = devicepair[8] |
| 163 | |
| 164 | if ntype not in ndevtypes: |
| 165 | ndevtypes.append(ntype) |
| 166 | if ptype not in pdevtypes: |
| 167 | pdevtypes.append(ptype) |
| 168 | |
| 169 | if vtype == '1v8': |
| 170 | volt = voltages1v8[vidx] |
| 171 | else: |
| 172 | volt = voltages3v3[vidx] |
| 173 | |
| 174 | if not nosim: |
| 175 | |
| 176 | # Read template and generate SPICE simulation netlist |
| 177 | newlines = [] |
| 178 | with open('circuit_template.spi', 'r') as ifile: |
| 179 | template = ifile.read().splitlines() |
| 180 | for line in template: |
| 181 | outline = re.sub('CORNER', corner, line) |
| 182 | outline = re.sub('FULL_VOLTAGE', str(volt), outline) |
| 183 | outline = re.sub('HALF_VOLTAGE', str(volt / 2.0), outline) |
| 184 | outline = re.sub('TEMPERATURE', str(temp), outline) |
| 185 | outline = re.sub('DEVICENAME_N', ntype, outline) |
| 186 | outline = re.sub('DEVICENAME_P', ptype, outline) |
| 187 | outline = re.sub('WIDTH_N', str(nwidth), outline) |
| 188 | outline = re.sub('WIDTH_P', str(pwidth), outline) |
| 189 | outline = re.sub('LENGTH_N', str(nlength), outline) |
| 190 | outline = re.sub('LENGTH_P', str(plength), outline) |
| 191 | outline = re.sub('LOADCAP', str(loadcap), outline) |
| 192 | newlines.append(outline) |
| 193 | |
| 194 | simname = 'sky130_' + corner + '_' + vname + '_' + tname + '_devpair' + str(devidx) + '.spice' |
| 195 | |
| 196 | with open(simname, 'w') as ofile: |
| 197 | for line in newlines: |
| 198 | print(line, file=ofile) |
| 199 | |
| 200 | generated_files.append(simname) |
| 201 | |
| 202 | # Run ngspice simulation |
| 203 | |
| 204 | print('** Running simulation on ' + devset + '(file ' + simname + ')') |
| 205 | print('** Conditions: temp=' + tname + ' corner=' + corner |
| 206 | + ' volt=' + vname) |
| 207 | p = subprocess.run(['ngspice', simname], |
| 208 | stdout = subprocess.PIPE, |
| 209 | universal_newlines = True) |
| 210 | |
| 211 | if p.stdout: |
| 212 | parameters = p.stdout.splitlines() |
| 213 | |
| 214 | for parameter in parameters: |
| 215 | valueline = parameter.split() |
| 216 | if len(valueline) < 3: |
| 217 | continue |
| 218 | if valueline[0] == 'ndynh': |
| 219 | try: |
| 220 | ndynh[ntype] |
| 221 | except: |
| 222 | ndynh[ntype] = valueline[2] |
| 223 | elif valueline[0] == 'pdynh': |
| 224 | try: |
| 225 | pdynh[ptype] |
| 226 | except: |
| 227 | pdynh[ptype] = valueline[2] |
| 228 | elif valueline[0] == 'ndynl': |
| 229 | try: |
| 230 | ndynl[ntype] |
| 231 | except: |
| 232 | ndynl[ntype] = valueline[2] |
| 233 | elif valueline[0] == 'pdynl': |
| 234 | try: |
| 235 | pdynl[ptype] |
| 236 | except: |
| 237 | pdynl[ptype] = valueline[2] |
| 238 | elif valueline[0] == 'nstat': |
| 239 | try: |
| 240 | nstat[ntype] |
| 241 | except: |
| 242 | nstat[ntype] = valueline[2] |
| 243 | elif valueline[0] == 'pstat': |
| 244 | try: |
| 245 | pstat[ptype] |
| 246 | except: |
| 247 | pstat[ptype] = valueline[2] |
| 248 | else: |
| 249 | print('** No file ' + outname + '; skipping.') |
| 250 | |
| 251 | paramfile = 'sky130_' + corner + '_' + vname + '_' + tname + '.prm' |
| 252 | |
| 253 | with open(paramfile, 'w') as ofile: |
| 254 | for line in hlines: |
| 255 | print(line, file=ofile) |
| 256 | |
| 257 | # Now output information for every device |
| 258 | print('', file=ofile) |
| 259 | |
| 260 | for device in ndevtypes: |
| 261 | devicepair = next(item for item in devices if item[2] == device) |
| 262 | if not devicepair: |
| 263 | print('Error: Bad entry for nFET device ' + device + '.\n') |
| 264 | continue |
| 265 | devset = devicepair[0] |
| 266 | if len(devicepair) != 9: |
| 267 | print('Error: Bad entry for device set ' + devset + '.\n') |
| 268 | continue |
| 269 | ntype = devicepair[2] |
| 270 | vtype = devicepair[3] |
| 271 | nlength = devicepair[6] |
| 272 | nwidth = devicepair[7] |
| 273 | loadcap = devicepair[8] |
| 274 | |
| 275 | print('; C=' + str(loadcap) + ', N(w=' + str(nwidth) + ', l=' + str(nlength) + ')', file=ofile) |
| 276 | print('resistance ' + ntype + ' dynamic-high ' + str(nwidth) + ' ' + str(nlength) + ' ' + ndynh[ntype], file=ofile) |
| 277 | print('resistance ' + ntype + ' dynamic-low ' + str(nwidth) + ' ' + str(nlength) + ' ' + ndynl[ntype], file=ofile) |
| 278 | print('resistance ' + ntype + ' static ' + str(nwidth) + ' ' + str(nlength) + ' ' + nstat[ntype], file=ofile) |
| 279 | print('', file=ofile) |
| 280 | |
| 281 | for device in pdevtypes: |
| 282 | devicepair = next(item for item in devices if item[1] == device) |
| 283 | if not devicepair: |
| 284 | print('Error: Bad entry for pFET device ' + device + '.\n') |
| 285 | continue |
| 286 | devset = devicepair[0] |
| 287 | if len(devicepair) != 9: |
| 288 | print('Error: Bad entry for device set ' + devset + '.\n') |
| 289 | continue |
| 290 | ptype = devicepair[1] |
| 291 | vtype = devicepair[3] |
| 292 | plength = devicepair[4] |
| 293 | pwidth = devicepair[5] |
| 294 | loadcap = devicepair[8] |
| 295 | |
| 296 | print('; C=' + str(loadcap) + ', P(w=' + str(pwidth) + ', l=' + str(plength) + ')', file=ofile) |
| 297 | print('resistance ' + ptype + ' dynamic-high ' + str(pwidth) + ' ' + str(plength) + ' ' + pdynh[ptype], file=ofile) |
| 298 | print('resistance ' + ptype + ' dynamic-low ' + str(pwidth) + ' ' + str(plength) + ' ' + pdynl[ptype], file=ofile) |
| 299 | print('resistance ' + ptype + ' static ' + str(pwidth) + ' ' + str(plength) + ' ' + pstat[ptype], file=ofile) |
| 300 | print('', file=ofile) |
| 301 | |
| 302 | if not keep: |
| 303 | print('**Removing generated intermediate files.') |
| 304 | for file in generated_files: |
| 305 | try: |
| 306 | os.remove(file) |
| 307 | except: |
| 308 | pass |
| 309 | |
| 310 | sys.exit(0) |