Additional changes to the spectre_to_spice.py script in support of onboarding the continuous SPICE models. Also: Added "-j" option to "make timing" for the PDK repository download and update, so that "make timing", which takes a long time, can run a lot faster.
diff --git a/Makefile.in b/Makefile.in index 65efa56..9adbc77 100644 --- a/Makefile.in +++ b/Makefile.in
@@ -117,7 +117,7 @@ #--------------------------------------------------- tech-%: % - (cd $* && ${MAKE} all) + (cd $* && ${MAKE} -j$(nproc) all) #---------------------------------------------------
diff --git a/VERSION b/VERSION index ca94b4b..ea13f22 100644 --- a/VERSION +++ b/VERSION
@@ -1 +1 @@ -1.0.298 +1.0.299
diff --git a/common/spectre_to_spice.py b/common/spectre_to_spice.py index c95f7ce..52698e0 100755 --- a/common/spectre_to_spice.py +++ b/common/spectre_to_spice.py
@@ -27,7 +27,7 @@ # If inside a subcircuit, remove the keyword "parameters". If outside, # change it to ".param" -def parse_param_line(line, inparam, insub, iscall, ispassed, linenum): +def parse_param_line(line, inparam, insub, iscall, ispassed, inmod, linenum): # Regexp patterns parm1rex = re.compile('[ \t]*parameters[ \t]*(.*)') @@ -77,7 +77,7 @@ pmatch = parm4rex.match(rest) if pmatch: - if ispassed and not iscall: + if ispassed and not iscall and not inmod: # End of passed parameters. Break line and generate ".param" ispassed = False fmtline.append('\n.param ') @@ -153,7 +153,7 @@ pmatch = parm5rex.match(rest) if pmatch: - # NOTE: Something that is not a parameters name should be + # NOTE: Something that is not a parameter name should be # extended from the previous line. Note that this parsing # is not rigorous and is possible to break. . . if any((c in '+-*/(){}^~!') for c in pmatch.group(1).strip()): @@ -271,8 +271,8 @@ caprex = re.compile('c([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*capacitor[ \t]*(.*)', re.IGNORECASE) resrex = re.compile('r([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*resistor[ \t]*(.*)', re.IGNORECASE) cdlrex = re.compile('[ \t]*([npcrdlmqx])([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE) - stddevrex = re.compile('[ \t]*([cr])([^ \t]+)[ \t]+([^ \t]+[ \t]+[^ \t]+)[ \t]+([^ \t]+)[ \t]*(.*)', re.IGNORECASE) - stddev2rex = re.compile('[ \t]*([cr])([^ \t]+)[ \t]+([^ \t]+[ \t]+[^ \t]+)[ \t]+([^ \t\'{]+[\'{][^\'}]+[\'}])[ \t]*(.*)', re.IGNORECASE) + stddevrex = re.compile('[ \t]*([crd])([^ \t]+)[ \t]+([^ \t]+[ \t]+[^ \t]+)[ \t]+([^ \t]+)[ \t]*(.*)', re.IGNORECASE) + stddev2rex = re.compile('[ \t]*([crd])([^ \t]+)[ \t]+([^ \t]+[ \t]+[^ \t]+)[ \t]+([^ \t\'{]+[\'{][^\'}]+[\'}])[ \t]*(.*)', re.IGNORECASE) stddev3rex = re.compile('[ \t]*([npcrdlmqx])([^ \t]+)[ \t]+(.*)', re.IGNORECASE) with open(in_file, 'r') as ifile: @@ -282,6 +282,26 @@ print('Failure to read ' + in_file + '; not an ASCII file?') return + #--------------------------------------------------------------- + # Do one pass through the file and pick up all subcircuit names, + # because any subcircuit call could be a forward reference. + #--------------------------------------------------------------- + + allsubrex = re.compile('.*subckt[ \t]+([^ \t\(]+)') + subnames = [] + for line in speclines: + testline = line.strip() + if testline.startswith('//') or testline.startswith('*'): + continue + smatch = allsubrex.match(testline) + if smatch: + subnames.append(smatch.group(1)) + + #--------------------------------------------------------------- + # Now do the main pass (this is probably a bad idea and it would + # have been more efficient to do this in multiple passes). + #--------------------------------------------------------------- + insub = False inparam = False inmodel = False @@ -295,7 +315,6 @@ savematch = None blockskip = 0 subname = '' - subnames = [] modname = '' modtype = '' othermodnames = [] @@ -338,8 +357,8 @@ if line.strip().startswith('+'): contline = True else: - contline = False if line.strip() != '': + contline = False if inparam: inparam = False if inpinlist: @@ -374,7 +393,7 @@ if contline: if inparam: # Continue handling parameters - fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed, linenum) + fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed, inmodel, linenum) if fmtline != '': if modellines != []: modellines.append(fmtline) @@ -398,7 +417,7 @@ # If inside a subcircuit, remove "parameters". If outside, # change it to ".param" - fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed, linenum) + fmtline, ispassed = parse_param_line(line, inparam, insub, False, ispassed, inmodel, linenum) if fmtline != '': inparam = True spicelines.append(fmtline) @@ -435,7 +454,7 @@ inmodel = 1 # Continue to "if inmodel == 1" block below else: - fmtline, ispassed = parse_param_line(mmatch.group(3), True, False, True, ispassed, linenum) + fmtline, ispassed = parse_param_line(mmatch.group(3), True, False, True, ispassed, inmodel, linenum) if isspectre and (modtype == 'resistor' or modtype == 'r2'): modtype = 'r' if isspectre and (modtype == 'diode'): @@ -445,9 +464,7 @@ # converted to "nmos" or "pmos" based on the "type" parameter modtype = 'ZYXW' modellines.append('.model ' + modname + ' ' + modtype + ' ' + fmtline) - if fmtline != '': - inparam = True - + inparam = True inmodel = 2 continue @@ -474,7 +491,6 @@ insub = True ispassed = True subname = imatch.group(1) - subnames.append(subname) if isspectre: devrex = re.compile('[ \t]*' + subname + '[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE) else: @@ -569,6 +585,10 @@ elif 'capacitor' in pnames[1:]: pnames.remove('capacitor') line = 'C' + ' '.join(pnames) + elif len(line) > 1 and line[0].lower() != 'x' and line[0] != '*' and line[0] != '+': + # NOTE: Assumes that anything else being called + # is a known subcircuit (probably better to check) + line = 'X' + ' '.join(pnames) spicelines.append(line) calllines = [] @@ -625,7 +645,7 @@ # Check for devices R and C. dmatch = caprex.match(line) if dmatch: - fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed, linenum) + fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed, inmodel, linenum) if fmtline != '': inparam = True spicelines.append('c' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline) @@ -636,7 +656,7 @@ dmatch = resrex.match(line) if dmatch: - fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed, linenum) + fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed, inmodel, linenum) if fmtline != '': inparam = True spicelines.append('r' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline) @@ -662,6 +682,10 @@ elif devmodel == 'resistor': devtype = 'r' devmodel = '' + # The "device model" might be a known (inline) subcircuit in + # which case this is really a subcircuit. + elif devmodel in subnames: + devtype = 'x' + devtype elif devtype.lower() == 'n' or devtype.lower() == 'p': # May be specific to SkyWater models, or is it a spectreism? # NOTE: There is a check, below, to revisit this assignment @@ -696,7 +720,7 @@ if devmodel.strip("'") == devmodel: devmodel = "'" + devmodel + "'" - fmtline, ispassed = parse_param_line(cmatch.group(5), True, insub, True, ispassed, linenum) + fmtline, ispassed = parse_param_line(cmatch.group(5), True, insub, True, ispassed, inmodel, linenum) if fmtline != '': inparam = True spicelines.append(devtype + cmatch.group(2) + ' ' + cmatch.group(3) + ' ' + devmodel + ' ' + fmtline) @@ -714,7 +738,7 @@ dmatch = devrex.match(line) if dmatch: - fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed, linenum) + fmtline, ispassed = parse_param_line(dmatch.group(3), True, insub, True, ispassed, inmodel, linenum) if fmtline != '': inparam = True calllines.append(subname + ' ' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + fmtline) @@ -753,7 +777,7 @@ else: continue - fmtline, ispassed = parse_param_line(line, True, True, False, ispassed, linenum) + fmtline, ispassed = parse_param_line(line, True, True, False, ispassed, inmodel, linenum) if fmtline != '': modellines.append(fmtline) continue @@ -777,14 +801,14 @@ if len(line) > 1 and line[0].lower() == 'd': spicelines[j] = perimrex.sub('pj\g<1>', line) - # Catching the spectre use of "m" is difficult, so do a 2nd pass + # Catching the spectre use of "m" is difficult, so do another pass # on "spicelines" to catch which subcircuits use "*m" expressions, # then for those subcircuits, add "mult=1" to the subcircuit # parameters. (NOTE: Need a more generic regular expression here) sqrtrex = re.compile('.*(sqrt\([ \t]*[^ \t]+[ \t]*\*[ \t]*)m[ \t]*\)', re.IGNORECASE) sqsubrex = re.compile('(sqrt\([ \t]*[^ \t]+[ \t]*\*[ \t]*)m[ \t]*\)', re.IGNORECASE) - subrex = re.compile('\.subckt[ \t]+([^ \t\(]+)[ \t]*') + subrex = re.compile('\.subckt[ \t]+([^ \t\(]+)[ \t]*', re.IGNORECASE) needmult = [] for j in range(len(spicelines)): line = spicelines[j] @@ -803,11 +827,22 @@ if smatch: spicelines[j] = line + ' mult=1' + # Check for resistor models using "tc1r" and "tc2r" + rmodrex = re.compile('.*[ \t]+tc[12]r[ \t]+=', re.IGNORECASE) + for j in range(len(spicelines)): + line = spicelines[j] + rmatch = rmodrex.match(line) + if rmatch: + spicelines[j] = re.sub('([ \t]+tc[12])r([ \t])', '\g<1>\g<2>', line) + # Output the result to out_file. with open(out_file, 'w') as ofile: for line in spicelines: print(line, file=ofile) + # Debug info + # print('Defined subcircuits: ' + ' '.join(subnames)) + if __name__ == '__main__': debug = False
diff --git a/sky130/custom/scripts/pdk_download.sh b/sky130/custom/scripts/pdk_download.sh index 70f9ae0..a8e8804 100755 --- a/sky130/custom/scripts/pdk_download.sh +++ b/sky130/custom/scripts/pdk_download.sh
@@ -29,4 +29,4 @@ # Generate liberty files echo "Building liberty timing files" -make timing +make -j$(nproc) timing
diff --git a/sky130/custom/scripts/pdk_update.sh b/sky130/custom/scripts/pdk_update.sh index c6bb970..dd8b66b 100755 --- a/sky130/custom/scripts/pdk_update.sh +++ b/sky130/custom/scripts/pdk_update.sh
@@ -28,4 +28,4 @@ # Regenerate liberty files echo "Regenerating liberty timing files" -make timing +make -j$(nproc) timing