Wrote a script to directly translate the GF180MCU I/O library CDL
to SPICE, rather than implementing the rather circular method of
having magic generate SPICE from extracted layout.
diff --git a/VERSION b/VERSION
index 12e7049..92be23d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.373
+1.0.374
diff --git a/gf180mcu/Makefile.in b/gf180mcu/Makefile.in
index 44135eb..9741d6f 100644
--- a/gf180mcu/Makefile.in
+++ b/gf180mcu/Makefile.in
@@ -994,6 +994,8 @@
 		-liberty cells/*/*_tt_025C_5v00.lib compile-only \
                         header=liberty/gf180mcu_fd_io__tt_025C_5v00.lib \
 			rename=gf180mcu_fd_io__tt_025C_5v00 \
+		-spice cells/*/*.cdl compile-only \
+			filter=custom/scripts/convert_io_cdl.py \
 		-gds cells/*/*_${$*_STACK}.gds compile-only \
 			options=custom/scripts/gds_import_io.tcl \
 		-lef cells/*/*_${$*_STACK}.lef \
diff --git a/gf180mcu/custom/scripts/convert_io_cdl.py b/gf180mcu/custom/scripts/convert_io_cdl.py
new file mode 100755
index 0000000..fcbc9b3
--- /dev/null
+++ b/gf180mcu/custom/scripts/convert_io_cdl.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+#
+# convert_io_cdl.py ---
+#
+# This script converts the GF CDL for the I/O pads to turn it into valid
+# SPICE syntax, using regular expression parsing.
+
+# This script is a filter to be run by setting the name of this script as
+# the value to "filter=" for the model install in the sky130 Makefile.
+
+import re
+import os
+import sys
+
+def filter(inname, outname):
+
+    # Read input
+    try:
+        with open(inname, 'r') as inFile:
+            spitext = inFile.read()
+            # (Don't) unwrap continuation lines
+            # spilines = spitext.replace('\n+', ' ').splitlines()
+            spilines = spitext.splitlines()
+    except:
+        print('convert_io_cdl.py: failed to open ' + inname + ' for reading.', file=sys.stderr)
+        return 1
+
+    fixedlines = []
+    modified = False
+
+    resrex = re.compile('^R', re.IGNORECASE)
+    caprex = re.compile('^C', re.IGNORECASE)
+
+    for line in spilines:
+        fixedline = line
+        isres = resrex.match(fixedline)
+        iscap = caprex.match(fixedline)
+        # check if resistor or capacitor for parameter name changes
+        # regexp substitutions:
+        # 1) Lines starting with R, C, or M --> change to X
+        fixedline = re.sub('^[RCM]', 'X', fixedline, flags=re.IGNORECASE)
+        # 2) Names in $[...] --> remove the delimiter
+        fixedline = re.sub(' \$\[', ' ', fixedline, flags=re.IGNORECASE)
+        fixedline = re.sub('\] ', ' ', fixedline, flags=re.IGNORECASE)
+        # 3) Remove $SUB=
+        fixedline = re.sub('\$SUB=', '', fixedline, flags=re.IGNORECASE)
+        # 4) Handle $W and $L for resistors
+        if isres:
+            fixedline = re.sub('\$W', 'r_width', fixedline, flags=re.IGNORECASE)
+            fixedline = re.sub('\$L', 'r_length', fixedline, flags=re.IGNORECASE)
+        if iscap:
+            fixedline = re.sub('w=', 'c_width=', fixedline, flags=re.IGNORECASE)
+            fixedline = re.sub('l=', 'c_length=', fixedline, flags=re.IGNORECASE)
+        # 5) Lowercase AREA and PJ
+        fixedline = re.sub('AREA', 'area', fixedline)
+        fixedline = re.sub('PJ', 'pj', fixedline)
+
+        if line != fixedline:
+            modified = True
+        fixedlines.append(fixedline)
+
+    # Write output
+    if outname == None:
+        for i in fixedlines:
+            print(i)
+    else:
+        # If the output is a symbolic link but no modifications have been made,
+        # then leave it alone.  If it was modified, then remove the symbolic
+        # link before writing.
+        if os.path.islink(outname):
+            if not modified:
+                return 0
+            else:
+                os.unlink(outname)
+        try:
+            with open(outname, 'w') as outFile:
+                for i in fixedlines:
+                    print(i, file=outFile)
+        except:
+            print('convert_io_cdl.py: failed to open ' + outname + ' for writing.', file=sys.stderr)
+            return 1
+
+
+if __name__ == '__main__':
+
+    # This script expects to get one or two arguments.  One argument is
+    # mandatory and is the input file.  The other argument is optional and
+    # is the output file.  The output file and input file may be the same
+    # name, in which case the original input is overwritten.
+
+    options = []
+    arguments = []
+    for item in sys.argv[1:]:
+        if item.find('-', 0) == 0:
+            options.append(item[1:])
+        else:
+            arguments.append(item)
+
+    if len(arguments) > 0:
+        infilename = arguments[0]
+
+    if len(arguments) > 1:
+        outfilename = arguments[1]
+    else:
+        outfilename = None
+
+    result = filter(infilename, outfilename)
+    sys.exit(result)
diff --git a/gf180mcu/gf180mcu.json b/gf180mcu/gf180mcu.json
index 60d052e..0b3d151 100644
--- a/gf180mcu/gf180mcu.json
+++ b/gf180mcu/gf180mcu.json
@@ -84,8 +84,8 @@
         "magic": "MAGIC_COMMIT"
     },
     "reference": {
-        "open_pdks": "17859a0fb11c75f1be7cbfdd70c947ee4c5ad6f9",
-        "magic": "86b4ac3e4c7a2141f37bf4d8760cc69aa2c23bac",
+        "open_pdks": "7c3797bed470ebecf64c8f155fbb7dc3e08d6f48",
+        "magic": "13a1bfcc2e30f063b63fd34be501820b77677bf5",
         "gf180mcu_pdk": "a897aa30369d3bcec87d9d50ce9b01f320f854ef",
         "gf180mcu_fd_pr": "612c346a3600bec3387e2974c8cdc692215be107",
         "gf180mcu_fd_io": "2aeec51ea2824b6cc0b396acfc39f4535f40b23a",