Corrections, mostly to spectre_to_spice to handle various issues, especially with the use of primitive device prefixes 'C' and 'R' to call subcircuits.
diff --git a/VERSION b/VERSION index 8b54409..4c24bf1 100644 --- a/VERSION +++ b/VERSION
@@ -1 +1 @@ -1.0.28 +1.0.29
diff --git a/common/spectre_to_spice.py b/common/spectre_to_spice.py index a89cdd4..807d996 100755 --- a/common/spectre_to_spice.py +++ b/common/spectre_to_spice.py
@@ -68,6 +68,13 @@ return '', ispassed while rest != '': + if iscall: + # It is hard to believe that this is legal even in spectre. + # Parameter expression given with no braces or quotes around + # the expression. Fix the expression by removing the spaces + # around '*'. + rest = re.sub('[ \t]*\*[ \t]*', '*', rest) + pmatch = parm4rex.match(rest) if pmatch: if ispassed: @@ -136,6 +143,28 @@ return ' '.join(fmtline), ispassed +def get_param_names(line): + # Find parameter names in a ".param" line and return a list of them. + # This is used to check if a bare word parameter name is passed to + # a capacitor or resistor device in the position of a value but + # without delimiters, so that it cannot be distinguished from a + # model name. There are only a few instances of this, so this + # routine is not rigorously checking all parameters, just entries + # on lines with ".param". + parmrex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([^ \t]+)[ \t]*(.*)') + rest = line + paramnames = [] + while rest != '': + pmatch = parmrex.match(rest) + if pmatch: + paramnames.append(pmatch.group(1)) + rest = pmatch.group(3) + else: + break + return paramnames + +# Run the spectre-to-ngspice conversion + def convert_file(in_file, out_file): # Regexp patterns @@ -149,6 +178,8 @@ cdlmodelrex = re.compile('[ \t]*model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.*)') binrex = re.compile('[ \t]*([0-9]+):[ \t]+type[ \t]*=[ \t]*(.*)') shincrex = re.compile('\.inc[ \t]+') + isexprrex = re.compile('[^0-9a-zA-Z_]') + paramrex = re.compile('\.param[ \t]+(.*)') stdsubrex = re.compile('\.subckt[ \t]+([^ \t]+)[ \t]+(.*)') stdmodelrex = re.compile('\.model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.*)') @@ -158,7 +189,8 @@ # Devices (resistor, capacitor, subcircuit as resistor or capacitor) 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]*([crdlmqx])([^ \t]+)[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \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) with open(in_file, 'r') as ifile: try: @@ -176,9 +208,11 @@ spicelines = [] calllines = [] modellines = [] + paramnames = [] savematch = None blockskip = 0 subname = '' + subnames = [] modname = '' modtype = '' @@ -345,6 +379,7 @@ insub = True ispassed = True subname = imatch.group(1) + subnames.append(subname) if isspectre: devrex = re.compile(subname + '[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE) else: @@ -428,6 +463,7 @@ insub = False inmodel = False subname = '' + paramnames = [] continue # Check for close of model @@ -460,6 +496,10 @@ continue cmatch = cdlrex.match(line) + if not cmatch: + if not isspectre: + cmatch = stddevrex.match(line) + if cmatch: ispassed = False devtype = cmatch.group(1) @@ -478,6 +518,20 @@ # necessary to find the model and discover that the # model is a resistor and not a subcircuit. devtype = 'r' + elif devtype.lower() == 'n' or devtype.lower() == 'p': + # May be specific to SkyWater models, or is it a spectreism? + devtype = 'x' + devtype + + # If a capacitor or resistor value is a parameter or expression, + # it must be enclosed in single quotes. Otherwise, if the named + # device model is a subcircuit, then the devtype must be "x". + + elif devtype.lower() == 'c' or devtype.lower() == 'r': + if devmodel in subnames: + devtype = 'x' + devtype + elif devmodel in paramnames or isexprrex.search(devmodel): + if devmodel.strip("'") == devmodel: + devmodel = "'" + devmodel + "'" fmtline, ispassed = parse_param_line(cmatch.group(5), True, insub, True, ispassed) if fmtline != '': @@ -488,6 +542,11 @@ spicelines.append(devtype + cmatch.group(2) + ' ' + cmatch.group(3) + ' ' + devmodel + ' ' + cmatch.group(5)) continue + # Check for a .param line and collect parameter names + pmatch = paramrex.match(line) + if pmatch: + paramnames.extend(get_param_names(pmatch.group(1))) + # Check for a line that begins with the subcircuit name dmatch = devrex.match(line)
diff --git a/common/split_one_spice.py b/common/split_one_spice.py index c454c8d..86a448a 100755 --- a/common/split_one_spice.py +++ b/common/split_one_spice.py
@@ -294,10 +294,13 @@ # All comment lines that are surrounded by lines marked -3 should # also be marked -3. This keeps comments that are completely inside # blocks that are only in the common file out of the individual files. + # ignore "* statistics" and "* mismatch" lines. lineno = 0 for line in inplines[1:]: lineno += 1 + if line.startswith('*') and ('statistics' in line or 'mismatch' in line): + continue if linedest[lineno] == -1 and linedest[lineno - 1] == -3: testline = lineno + 1 while linedest[testline] == -1:
diff --git a/sky130/magic/sky130.tech b/sky130/magic/sky130.tech index 7e31c4c..22125ce 100644 --- a/sky130/magic/sky130.tech +++ b/sky130/magic/sky130.tech
@@ -1638,16 +1638,16 @@ or fomfill_pass2 or fomfill_coarse or fomfill_fine - calma 23 0 templayer polyfill polyfill_pass1 or polyfill_coarse or polyfill_medium or polyfill_fine - calma 28 0 layer FOMMASK fomfill + calma 23 0 layer POLYMASK polyfill + calma 28 0 #--------------------------------------------------- # MET1 fill