Updated the convert_spectre.py script to correct a number of errors,
especially related to CDL formats which may appear in spectre files.
Added a "split_spice.py" script to take the output of "convert_spectre.py"
and pull the subcircuit definitions out of it and put them in separate
files.
diff --git a/common/convert_spectre.py b/common/convert_spectre.py
index 9127712..a207bd5 100755
--- a/common/convert_spectre.py
+++ b/common/convert_spectre.py
@@ -30,14 +30,17 @@
def parse_param_line(line, inparam, insub):
# Regexp patterns
- parm1rex = re.compile('parameters' + '[ \t]*(.*)')
- parm2rex = re.compile('\+[ \t]*(.*)')
- parm3rex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([^ \t]+)[ \t]*(.*)')
+ parm1rex = re.compile('[ \t]*parameters[ \t]*(.*)')
+ parm2rex = re.compile('[ \t]*params:[ \t]*(.*)')
+ parm3rex = re.compile('\+[ \t]*(.*)')
+ parm4rex = re.compile('[ \t]*([^= \t]+)[ \t]*=[ \t]*([^ \t]+)[ \t]*(.*)')
+ parm5rex = re.compile('[ \t]*([^= \t]+)[ \t]*(.*)')
fmtline = []
+ iscdl = False
if inparam:
- pmatch = parm2rex.match(line)
+ pmatch = parm3rex.match(line)
if pmatch:
fmtline.append('+')
rest = pmatch.group(1)
@@ -52,10 +55,19 @@
fmtline.append('.param')
rest = pmatch.group(1)
else:
- return ''
+ pmatch = parm2rex.match(line)
+ if pmatch:
+ if insub:
+ fmtline.append('+')
+ else:
+ return ''
+ rest = pmatch.group(1)
+ iscdl = True
+ else:
+ return ''
while rest != '':
- pmatch = parm3rex.match(rest)
+ pmatch = parm4rex.match(rest)
if pmatch:
fmtline.append(pmatch.group(1))
fmtline.append('=')
@@ -72,10 +84,18 @@
# ngspice. So put them in an in-line comment
if rest != '':
- nmatch = parm3rex.match(rest)
+ nmatch = parm4rex.match(rest)
if not nmatch:
fmtline.append(' $ ' + rest.replace(' ', '').replace('\t', ''))
rest = ''
+ elif iscdl:
+ # Match to a CDL subckt parameter that does not have an '=' and so
+ # assumes that the parameter is always passed. That is not legal SPICE,
+ # so supply a default value of 1.
+ pmatch = parm5rex.match(rest)
+ if pmatch:
+ fmtline.append(pmatch.group(1) + '=1')
+ rest = pmatch.group(2)
else:
break
@@ -87,6 +107,7 @@
statrex = re.compile('[ \t]*statistics[ \t]*\{(.*)')
simrex = re.compile('[ \t]*simulator[ \t]+([^= \t]+)[ \t]*=[ \t]*(.+)')
insubrex = re.compile('[ \t]*inline[ \t]+subckt[ \t]+([^ \t]+)[ \t]*\(([^)]*)')
+ cdlsubrex = re.compile('\.subckt[ \t]+([^ \t]+)[ \t]*\(([^)]*)')
endsubrex = re.compile('[ \t]*ends[ \t]+(.+)')
modelrex = re.compile('[ \t]*model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+\{(.*)')
binrex = re.compile('[ \t]*([0-9]+):[ \t]+type[ \t]*=[ \t]*(.*)')
@@ -99,6 +120,7 @@
# 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)
with open(in_file, 'r') as ifile:
speclines = ifile.read().splitlines()
@@ -107,7 +129,7 @@
inparam = False
inmodel = False
inpinlist = False
- isspectre = True
+ isspectre = False
spicelines = []
calllines = []
modellines = []
@@ -140,7 +162,7 @@
spicelines.append(line.strip())
continue
- # Item 3. Flag continuation lines
+ # Item 4. Flag continuation lines
if line.strip().startswith('+'):
contline = True
else:
@@ -150,7 +172,17 @@
if inpinlist:
inpinlist = False
- # Item 4. Count through { ... } blocks that are not SPICE syntax
+ # Item 3. Handle blank lines like comment lines
+ if line.strip() == '':
+ if modellines != []:
+ modellines.append(line.strip())
+ elif calllines != []:
+ calllines.append(line.strip())
+ else:
+ spicelines.append(line.strip())
+ continue
+
+ # Item 5. Count through { ... } blocks that are not SPICE syntax
if blockskip > 0:
# Warning: Assumes one brace per line, may or may not be true
if '{' in line:
@@ -165,7 +197,7 @@
spicelines.append('* ' + line)
continue
- # Item 5. Handle continuation lines
+ # Item 6. Handle continuation lines
if contline:
if inparam:
# Continue handling parameters
@@ -179,7 +211,7 @@
spicelines.append(fmtline)
continue
- # Item 6. Regexp matching
+ # Item 7. Regexp matching
# Catch "simulator lang="
smatch = simrex.match(line)
@@ -213,7 +245,6 @@
else:
mmatch = stdmodelrex.match(line)
if mmatch:
- modellines = []
modname = mmatch.group(1)
modtype = mmatch.group(2)
@@ -233,23 +264,23 @@
if isspectre:
imatch = insubrex.match(line)
else:
- imatch = stdsubrex.match(line)
+ # Check for CDL format .subckt lines first
+ imatch = cdlsubrex.match(line)
+ if not imatch:
+ imatch = stdsubrex.match(line)
if imatch:
insub = True
subname = imatch.group(1)
- calllines = []
if isspectre:
devrex = re.compile(subname + '[ \t]*\(([^)]*)\)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
- # If there is no close-parenthesis then we should expect it on
- # a continuation line
- inpinlist = True if ')' not in line else False
- # Remove parentheses groups from subcircuit arguments
- spicelines.append('.subckt ' + ' ' + subname + ' ' + imatch.group(2))
else:
devrex = re.compile(subname + '[ \t]*([^ \t]+)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
- inpinlist = True
- spicelines.append(line)
+ # If there is no close-parenthesis then we should expect it on
+ # a continuation line
+ inpinlist = True if ')' not in line else False
+ # Remove parentheses groups from subcircuit arguments
+ spicelines.append('.subckt ' + ' ' + subname + ' ' + imatch.group(2))
continue
else:
@@ -300,6 +331,7 @@
for line in calllines[1:]:
spicelines.append(line)
+ calllines = []
spicelines.append('.ends ' + subname)
@@ -307,6 +339,7 @@
spicelines.append('')
for line in modellines:
spicelines.append(line)
+ modellines = []
insub = False
inmodel = False
@@ -342,6 +375,18 @@
spicelines.append('r' + dmatch.group(1) + ' ' + dmatch.group(2) + ' ' + dmatch.group(3))
continue
+ if not isspectre:
+ cmatch = cdlrex.match(line)
+ if cmatch:
+ fmtline = parse_param_line(cmatch.group(5), True, insub)
+ if fmtline != '':
+ inparam = True
+ spicelines.append(cmatch.group(1) + cmatch.group(2) + ' ' + cmatch.group(3) + ' ' + cmatch.group(4) + ' ' + fmtline)
+ continue
+ else:
+ spicelines.append(cmatch.group(1) + cmatch.group(2) + ' ' + cmatch.group(3) + ' ' + cmatch.group(4) + ' ' + cmatch.group(5))
+ continue
+
# Check for a line that begins with the subcircuit name
dmatch = devrex.match(line)
diff --git a/common/split_gds.py b/common/split_gds.py
index a673c2f..5336d8e 100755
--- a/common/split_gds.py
+++ b/common/split_gds.py
@@ -1,4 +1,7 @@
#!/bin/env python3
+#
+# split_gds.py --
+#
# Script to read a GDS library and write into individual GDS files, one per cell
import os
diff --git a/common/split_spice.py b/common/split_spice.py
new file mode 100755
index 0000000..75cea9e
--- /dev/null
+++ b/common/split_spice.py
@@ -0,0 +1,216 @@
+#!/bin/env python3
+#
+# split_spice.py --
+#
+# Script that reads the SPICE output from the convert_spectre.py script,
+# which typically has parsed through files containing inline subcircuits
+# and recast them as normal SPICE .subckt ... .ends entries, and pulled
+# any model blocks inside the inline subckt out. This script removes
+# each .subckt ... .ends block from every file and moves it to its own
+# file named <subckt_name>.spice.
+#
+# The arguments are <path_to_input> and <path_to_output>. If there is
+# only one argument, or if <path_to_input> is equal to <path_to_output>,
+# then the new .spice files are added to the directory and the model
+# files are modified in place. Otherwise, all modified files are placed
+# in <path_to_output>.
+
+import os
+import sys
+import re
+import glob
+
+def usage():
+ print('split_spice.py <path_to_input> <path_to_output>')
+
+def convert_file(in_file, out_path, out_file):
+
+ # Regexp patterns
+ paramrex = re.compile('\.param[ \t]+(.*)')
+ subrex = re.compile('\.subckt[ \t]+([^ \t]+)[ \t]+([^ \t]*)')
+ modelrex = re.compile('\.model[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.*)')
+ endsubrex = re.compile('\.ends[ \t]+(.+)')
+ increx = re.compile('\.include[ \t]+')
+
+ with open(in_file, 'r') as ifile:
+ inplines = ifile.read().splitlines()
+
+ insubckt = False
+ inparam = False
+ inmodel = False
+ inpinlist = False
+ spicelines = []
+ subcktlines = []
+ savematch = None
+ subname = ''
+ modname = ''
+ modtype = ''
+
+ for line in inplines:
+
+ # Item 1. Handle comment lines
+ if line.startswith('*'):
+ if subcktlines != []:
+ subcktlines.append(line.strip())
+ else:
+ spicelines.append(line.strip())
+ continue
+
+ # Item 2. Flag continuation lines
+ if line.startswith('+'):
+ contline = True
+ else:
+ contline = False
+ if inparam:
+ inparam = False
+ if inpinlist:
+ inpinlist = False
+
+ # Item 3. Handle continuation lines
+ if contline:
+ if inparam:
+ # Continue handling parameters
+ if subcktlines != []:
+ subcktlines.append(line)
+ else:
+ spicelines.append(line)
+ continue
+
+ # Item 4. Regexp matching
+
+ # If inside a subcircuit, remove "parameters". If outside,
+ # change it to ".param"
+ pmatch = paramrex.match(line)
+ if pmatch:
+ inparam = True
+ if insubckt:
+ subcktlines.append(line)
+ else:
+ spicelines.append(line)
+ continue
+
+ # model
+ mmatch = modelrex.match(line)
+ if mmatch:
+ modellines = []
+ modname = mmatch.group(1)
+ modtype = mmatch.group(2)
+
+ spicelines.append(line)
+ inmodel = 2
+ continue
+
+ if not insubckt:
+ # Things to parse if not in a subcircuit
+
+ imatch = subrex.match(line)
+ if imatch:
+ insubckt = True
+ subname = imatch.group(1)
+ devrex = re.compile(subname + '[ \t]*([^ \t]+)[ \t]*([^ \t]+)[ \t]*(.*)', re.IGNORECASE)
+ inpinlist = True
+ subcktlines.append(line)
+ continue
+
+ else:
+ # Things to parse when inside of a ".subckt" block
+
+ if inpinlist:
+ # Watch for pin list continuation line.
+ subcktlines.append(line)
+ continue
+
+ else:
+ ematch = endsubrex.match(line)
+ if ematch:
+ if ematch.group(1) != subname:
+ print('Error: "ends" name does not match "subckt" name!')
+ print('"ends" name = ' + ematch.group(1))
+ print('"subckt" name = ' + subname)
+
+ subcktlines.append(line)
+
+ # Dump the contents of subcktlines into a file
+ subckt_file = subname + '.spice'
+ with open(out_path + '/' + subckt_file, 'w') as ofile:
+ print('* Subcircuit definition of cell ' + subname, file=ofile)
+ for line in subcktlines:
+ print(line, file=ofile)
+ subcktlines = []
+
+ insubckt = False
+ inmodel = False
+ subname = ''
+ continue
+
+ # Copy line as-is
+ if insubckt:
+ subcktlines.append(line)
+ else:
+ spicelines.append(line)
+
+ # Output the result to out_file.
+ with open(out_path + '/' + out_file, 'w') as ofile:
+ for line in spicelines:
+ print(line, file=ofile)
+
+if __name__ == '__main__':
+ debug = False
+
+ if len(sys.argv) == 1:
+ print("No options given to split_spice.py.")
+ usage()
+ sys.exit(0)
+
+ optionlist = []
+ arguments = []
+
+ for option in sys.argv[1:]:
+ if option.find('-', 0) == 0:
+ optionlist.append(option)
+ else:
+ arguments.append(option)
+
+ if len(arguments) != 2:
+ print("Wrong number of arguments given to split_spice.py.")
+ usage()
+ sys.exit(0)
+
+ if '-debug' in optionlist:
+ debug = True
+
+ inpath = arguments[0]
+ outpath = arguments[1]
+ do_one_file = False
+
+ if not os.path.exists(inpath):
+ print('No such source directory ' + inpath)
+ sys.exit(1)
+
+ if os.path.isfile(inpath):
+ do_one_file = True
+
+ if do_one_file:
+ if os.path.exists(outpath):
+ print('Error: File ' + outpath + ' exists.')
+ sys.exit(1)
+ convert_file(inpath, outpath)
+
+ else:
+ if not os.path.exists(outpath):
+ os.makedirs(outpath)
+
+ infilelist = glob.glob(inpath + '/*')
+
+ for filename in infilelist:
+ fileext = os.path.splitext(filename)[1]
+
+ # Ignore verilog or verilog-A files that might be in a model directory
+ if fileext == '.v' or fileext == '.va':
+ continue
+
+ froot = os.path.split(filename)[1]
+ convert_file(filename, outpath, froot)
+
+ print('Done.')
+ exit(0)