Reverted the previous commit which removed the installation of the
klayout .map file for sky130.  The problem was caused by a mix-up
of repository source locations on my end and the file should not
have been removed.  Also:  Updated the run-time python scripts for
the CACE system, but this is an intermediate stage of a work in
progress.
diff --git a/VERSION b/VERSION
index 3a6453e..2b52611 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.398
+1.0.399
diff --git a/runtime/cace.py b/runtime/cace.py
index 6106677..86f3eac 100755
--- a/runtime/cace.py
+++ b/runtime/cace.py
@@ -46,11 +46,12 @@
 from settings import Settings
 from simhints import SimHints
 
-import config
-
 # User preferences file (if it exists)
 prefsfile = '~/design/.profile/prefs.json'
 
+# Application path (path where this script is located)
+apps_path = os.path.realpath(os.path.dirname(__file__))
+
 #------------------------------------------------------
 # Simple dialog for confirming quit or upload
 #------------------------------------------------------
@@ -176,7 +177,7 @@
         self.help = HelpWindow(self, fontsize = fontsize)
 
         with io.StringIO() as buf, contextlib.redirect_stdout(buf):
-            self.help.add_pages_from_file(config.apps_path + '/characterize_help.txt')
+            self.help.add_pages_from_file(apps_path + '/characterize_help.txt')
             message = buf.getvalue()
 
         # Set the help display to the first page
@@ -220,21 +221,7 @@
         if 'username' in self.prefs:
             username = self.prefs['username']
         else:
-            userid = os.environ['USER']
-            p = subprocess.run(['/ef/apps/bin/withnet',
-			config.apps_path + '/og_uid_service.py', userid],
-                        stdout = subprocess.PIPE)
-            if p.stdout:
-                uid_string = p.stdout.splitlines()[0].decode('utf-8')
-                userspec = re.findall(r'[^"\s]\S*|".+?"', uid_string)
-                if len(userspec) > 0:
-                    username = userspec[0].strip('"')
-                    # Note userspec[1] = UID and userspec[2] = role, useful
-                    # for future applications.
-                else:
-                    username = userid
-            else:
-                username = userid
+            username = os.environ['USER']
 
         # Label with the user
         self.toppane.title_frame = ttk.Frame(self.toppane)
@@ -540,8 +527,7 @@
         datasheet = os.path.split(self.cur_datasheet)[1]
         designname = os.path.splitext(datasheet)[0]
         print('Cancel characterization of ' + designname + ' (' + dspath + ' )')
-        subprocess.run(['/ef/apps/bin/withnet',
-			config.apps_path + '/cace_design_upload.py', '-cancel',
+        subprocess.run([apps_path + '/cace_design_upload.py', '-cancel',
                         dspath])
         self.removeprogress()
         self.bbar.upload_button.configure(text='Submit', state = 'enabled',
@@ -704,7 +690,7 @@
                 return
             print('Upload selected')
 
-        # Save hints in file in spi/ directory.
+        # Save hints in file in spice/ directory.
         hintlist = []
         for eparam in dsheet['electrical-params']:
             if not 'editable' in eparam:
@@ -723,9 +709,7 @@
         if not self.settings.get_test():
             self.progress_bar_setup(dspath)
             self.update_idletasks()
-        subprocess.run(['/ef/apps/bin/withnet',
-			config.apps_path + '/cace_design_upload.py',
-                        dspath])
+        subprocess.run([apps_path + '/cace_design_upload.py', dspath])
 
         # Remove the settings file
         os.remove(dspath + '/settings.json')
@@ -1006,13 +990,17 @@
         self.stat_label.configure(text='(in progress)', style='blue.TLabel')
         # Update status now
         self.update_idletasks()
+
+        if dspath == '':
+            dspath = '.'
+
         print('Datasheet directory is = ' + dspath + '\n')
 
         # Instead of using the original datasheet, use the one in memory so that
         # it accumulates results.  A "save" button will update the original.
         if not os.path.isdir(dspath + '/ngspice'):
             os.makedirs(dspath + '/ngspice')
-        dsdir = dspath + '/ngspice/char'
+        dsdir = dspath + '/ngspice'
         if not os.path.isdir(dsdir):
             os.makedirs(dsdir)
         with open(dsdir + '/datasheet.json', 'w') as file:
@@ -1025,7 +1013,7 @@
         # Call cace_gensim with full set of options
         # First argument is the root directory
         # (Diagnostic)
-        design_path = dspath + '/spi'
+        design_path = dspath + '/spice'
 
         print('Calling cace_gensim.py ' + dspath + 
 			' -local -method=' + method)
@@ -1043,7 +1031,7 @@
         print(' -layoutdir=' + dspath + '/mag' + ' -testbenchdir=' + dspath + '/testbench')
         print(' -datasheet=datasheet.json')
         
-        self.caceproc = subprocess.Popen([config.apps_path + '/cace_gensim.py', dspath,
+        self.caceproc = subprocess.Popen([apps_path + '/cace_gensim.py', dspath,
 			*modetext,
 			'-method=' + method,  # Call local mode w/method
 			'-simdir=' + dsdir,
@@ -1194,7 +1182,9 @@
         # file if it predates the unannotated datasheet (that indicates
         # simulator failure, and no results).
         dspath = os.path.split(self.cur_datasheet)[0]
-        dsdir = dspath + '/ngspice/char'
+        if dspath == '':
+            dspath = '.'
+        dsdir = dspath + '/ngspice'
         anno = dsdir + '/datasheet_' + suffix + '.json'
         unanno = dsdir + '/datasheet.json'
 
@@ -1221,7 +1211,7 @@
     def save_results(self):
         # Write datasheet_save with all the locally processed results.
         dspath = os.path.split(self.cur_datasheet)[0]
-        dsdir = dspath + '/ngspice/char'
+        dsdir = dspath + '/ngspice'
 
         if self.origin.get() == 'Layout Extracted':
             jsonfile = dsdir + '/datasheet_lsave.json'
@@ -1255,7 +1245,7 @@
         # recent than 'datasheet_anno'.  If so, return True, else False.
 
         [dspath, dsname] = os.path.split(self.cur_datasheet)
-        dsdir = dspath + '/ngspice/char'
+        dsdir = dspath + '/ngspice'
 
         if self.origin.get() == 'Layout Extracted':
             savefile = dsdir + '/datasheet_lsave.json'
@@ -1338,7 +1328,7 @@
     def load_results(self, value={}):
         # Check if datasheet_save exists and is more recent than the
         # latest design netlist.  If so, load it;  otherwise, not.
-        # NOTE:  Name of .spi file comes from the project 'ip-name'
+        # NOTE:  Name of .spice file comes from the project 'ip-name'
         # in the datasheet.
 
         [dspath, dsname] = os.path.split(self.cur_datasheet)
@@ -1347,6 +1337,9 @@
         except KeyError:
             return
 
+        if dspath == '':
+            dspath = '.'
+
         dsroot = dsheet['ip-name']
 
         # Remove any existing results from the datasheet records
@@ -1357,15 +1350,19 @@
 
         # dsroot = os.path.splitext(dsname)[0]
 
-        dsdir = dspath + '/spi'
+        dsdir = dspath + '/spice'
+
+        if not os.path.exists(dsdir):
+            print('Error:  Cannot find directory spice/ in path ' + dspath)
+
         if self.origin.get() == 'Layout Extracted':
-            spifile = dsdir + '/pex/' + dsroot + '.spi'
+            spifile = dsdir + '/pex/' + dsroot + '.spice'
             savesuffix = 'lsave'
         else:
-            spifile = dsdir + '/' + dsroot + '.spi'
+            spifile = dsdir + '/' + dsroot + '.spice'
             savesuffix = 'save'
 
-        dsdir = dspath + '/ngspice/char'
+        dsdir = dspath + '/ngspice'
         savefile = dsdir + '/datasheet_' + savesuffix + '.json'
 
         if os.path.exists(savefile):
diff --git a/runtime/cace_datasheet_upload.py b/runtime/cace_datasheet_upload.py
index af5db63..f7fe9d2 100755
--- a/runtime/cace_datasheet_upload.py
+++ b/runtime/cace_datasheet_upload.py
@@ -17,8 +17,6 @@
 import file_compressor
 import file_request_hash
 
-import config
-
 """
  standalone script.
  Makes rest calls to marketplace REST server to save datasheet
@@ -27,7 +25,7 @@
  has no other side effects.
 """
 
-mktp_server_url = config.mktp_server_url
+mktp_server_url = ""
 
 # Make request to server sending json passed in.
 def send_doc(doc):
diff --git a/runtime/cace_design_upload.py b/runtime/cace_design_upload.py
index 4b4c47f..272deca 100755
--- a/runtime/cace_design_upload.py
+++ b/runtime/cace_design_upload.py
@@ -19,8 +19,6 @@
 import file_request_hash
 import local_uid_services
 
-import config
-
 """
  standalone script.
  Makes rest calls to marketplace REST server to save datasheet
@@ -30,8 +28,8 @@
  on the CACE server.
 """
 
-mktp_server_url = config.mktp_server_url
-cace_server_url = config.cace_server_url
+mktp_server_url = ""
+cace_server_url = ""
 
 # Make request to server sending json passed in.
 def send_doc(doc):
diff --git a/runtime/cace_gensim.py b/runtime/cace_gensim.py
index 603b28a..6b323bd 100755
--- a/runtime/cace_gensim.py
+++ b/runtime/cace_gensim.py
@@ -69,13 +69,10 @@
 import faulthandler
 from functools import reduce
 from spiceunits import spice_unit_convert
-from fix_libdirs import fix_libdirs
 
-import config
+# Application path (path where this script is located)
+apps_path = os.path.realpath(os.path.dirname(__file__))
 
-# Values obtained from config:
-#
-apps_path = config.apps_path
 launchproc = []
 
 def construct_dut_from_path(pname, pathname, pinlist, foundry, node):
@@ -107,16 +104,9 @@
             nlfoundry = lmatch.group(2)
             if nlfoundry != foundry:
                 print('Error:  Foundry is ' + foundry + ' in spec sheet, ' + nlfoundry + ' in netlist.')
-                # Not yet fixed in Electric
-                ## return ""
             if nlnode != node:
-                # Hack for legacy node name
-                if nlnode == 'XH035A' and node == 'XH035':
-                    pass
-                else:
-                    print('Error:  Node is ' + node + ' in spec sheet, ' + nlnode + ' in netlist.')
-                    # Not yet fixed in Electric
-                    ## return ""
+                print('Error:  Node is ' + node + ' in spec sheet, ' + nlnode + ' in netlist.')
+
         lmatch = subrex.match(line)
         if lmatch:
             rest = lmatch.group(1) 
@@ -131,11 +121,12 @@
                     except StopIteration:
                         # Maybe this is not the DUT?
                         found = 0
-                        # Try the next line
+                        # Try the next line (to be done)
                         break
                     else:
                         outline = outline + pin + ' '
                         found += 1
+                break
 
     if found == 0 and dutname == "":
         print('File ' + pathname + ' does not contain any subcircuits!')
@@ -413,13 +404,13 @@
     endrex = re.compile(r'[ \t]*\.end[ \t]*', re.IGNORECASE)
     endsrex = re.compile(r'[ \t]*\.ends[ \t]*', re.IGNORECASE)
     # IP names in the ridiculously complicated form
-    # <user_path>/design/ip/<proj_name>/<version>/<spi-type>/<proj_name>/<proj_netlist>
+    # <user_path>/design/ip/<proj_name>/<version>/<spice-type>/<proj_name>/<proj_netlist>
     ippathrex = re.compile(r'(.+)/design/ip/([^/]+)/([^/]+)/([^/]+)/([^/]+)/([^/ \t]+)')
     locpathrex = re.compile(r'(.+)/design/([^/]+)/spi/([^/]+)/([^/ \t]+)')
     # This form does not appear on servers but is used if an IP block is being prepared locally.
     altpathrex = re.compile(r'(.+)/design/([^/]+)/([^/]+)/([^/]+)/([^/ \t]+)')
     # Local IP names in the form
-    # <user_path>/design/<project>/spi/<spi-type>/<proj_netlist>
+    # <user_path>/design/<project>/spi/<spice-type>/<proj_netlist>
 
     # To be completed
     with open(filename, 'r') as ifile:
@@ -462,7 +453,7 @@
                     spitype = ippath.group(4)
                     ipname3 = ippath.group(5)
                     ipnetlist = ippath.group(6)
-                    funcpath = userpath + '/design/ip/' + ipname2 + '/' + ipversion + '/spi-func/' + ipname + '.spi' 
+                    funcpath = userpath + '/design/ip/' + ipname2 + '/' + ipversion + '/spice-func/' + ipname + '.spice'
                 else:
                     locpath = locpathrex.match(incpath)
                     if locpath:
@@ -470,7 +461,7 @@
                         ipname2 = locpath.group(2)
                         spitype = locpath.group(3)
                         ipnetlist = locpath.group(4)
-                        funcpath = userpath + '/design/' + ipname2 + '/spi/func/' + ipname + '.spi' 
+                        funcpath = userpath + '/design/' + ipname2 + '/spi/func/' + ipname + '.spice' 
                     else:
                         altpath = altpathrex.match(incpath)
                         if altpath:
@@ -479,7 +470,7 @@
                             spitype = altpath.group(3)
                             ipname3 = altpath.group(4)
                             ipnetlist = altpath.group(5)
-                            funcpath = userpath + '/design/' + ipname2 + '/spi/func/' + ipname + '.spi' 
+                            funcpath = userpath + '/design/' + ipname2 + '/spi/func/' + ipname + '.spice' 
                         
                 funcpath = os.path.expanduser(funcpath)
                 if funcpath and os.path.exists(funcpath):
@@ -553,6 +544,7 @@
     colonsepex = re.compile(r'^([^:]+):([^:]+)$')	# a:b (colon-separated values)
     vectrex = re.compile(r'([^\[]+)\[([0-9]+)\]')       # pin name is a vector signal
     vect2rex = re.compile(r'([^<]+)<([0-9]+)>')         # pin name is a vector signal (alternate style)
+    vect3rex = re.compile(r'([a-zA-Z][^0-9]*)([0-9]+)') # pin name is a vector signal (alternate style)
     libdirrex = re.compile(r'.lib[ \t]+(.*)[ \t]+')     # pick up library name from .lib
     vinclrex = re.compile(r'[ \t]*`include[ \t]+"([^"]+)"')	# verilog include statement
 
@@ -669,6 +661,7 @@
 
                     repl = []
                     no_repl_ok = False
+                    vtype = -1
                     sweeprec = sweepex.match(vpattern)
                     if sweeprec:
                         sweeptype = sweeprec.group(2)
@@ -687,11 +680,19 @@
                             if lmatch:
                                 pinidx = int(lmatch.group(2))
                                 vcondition = lmatch.group(1)
+                                vtype = 0
                             else:
                                 lmatch = vect2rex.match(condition)
                                 if lmatch:
                                     pinidx = int(lmatch.group(2))
                                     vcondition = lmatch.group(1)
+                                    vtype = 1
+                                else:
+                                    lmatch = vect3rex.match(condition)
+                                    if lmatch:
+                                        pinidx = int(lmatch.group(2))
+                                        vcondition = lmatch.group(1)
+                                        vtype = 3
                                 
                             try:
                                  entry = next((item for item in simval if item[0] == condition))
@@ -821,8 +822,18 @@
                                         try:
                                             entry = next((item for item in simval if item[0].split('[')[0].split('<')[0] == vcondition))
                                         except:
-                                            # if no match, subsline remains as-is.
-                                            pass
+                                            if vtype == 3:
+                                                for entry in simval:
+                                                    lmatch = vect3rex.match(entry[0])
+                                                    if lmatch:
+                                                        if lmatch.group(1) == vcondition:
+                                                            vlen = len(entry[2])
+                                                            uval = entry[2][(vlen - 1) - pinidx]
+                                                            repl = str(uval)
+                                                            break
+                                            else:
+                                                # if no match, subsline remains as-is.
+                                                pass
                                         else:
                                             # Handle as vector bit slice (see below)
                                             vlen = len(entry[2])
@@ -978,6 +989,7 @@
     pinlist = []
     vectrex = re.compile(r"([^\[]+)\[([0-9]+):([0-9]+)\]")
     vect2rex = re.compile(r"([^<]+)\<([0-9]+):([0-9]+)\>")
+    vect3rex = re.compile(r"([^0-9]+)([0-9]+):([0-9]+)")
     for pinrec in dsheet['pins']:
         vmatch = vectrex.match(pinrec['name'])
         if vmatch:
@@ -1005,7 +1017,20 @@
                     pinlist.append(newpinrec)
                     newpinrec['name'] = pinname + '<' + str(i) + '>'
             else:
-                pinlist.append(pinrec)
+                vmatch = vect3rex.match(pinrec['name'])
+                if vmatch:
+                    pinname = vmatch.group(1)
+                    pinmin = vmatch.group(2)
+                    pinmax = vmatch.group(3)
+                    if int(pinmin) > int(pinmax):
+                        pinmin = vmatch.group(3)
+                        pinmax = vmatch.group(2)
+                    for i in range(int(pinmin), int(pinmax) + 1):
+                        newpinrec = pinrec.copy()
+                        pinlist.append(newpinrec)
+                        newpinrec['name'] = pinname + str(i)
+                else:
+                    pinlist.append(pinrec)
 
     # Make sure all local conditions define a pin.  Those that are not
     # associated with a pin will have a null string for the pin name.
@@ -1162,21 +1187,21 @@
         # it and make substitutions
         # NOTE:  Schematic methods are bundled with the DUT schematic
 
-        template = testbenchpath + '/' + testbench.lower() + '.spi'
+        template = testbenchpath + '/' + testbench.lower() + '.spice'
 
         if testbench_orig and not os.path.isfile(template):
             print('Warning:  Alternate testbench ' + testbench + ' cannot be found.')
             print('Reverting to original testbench ' + testbench_orig)
             testbench = testbench_orig
             filename = testbench + fsuffix
-            template = testbenchpath + '/' + testbench.lower() + '.spi'
+            template = testbenchpath + '/' + testbench.lower() + '.spice'
 
         if os.path.isfile(template):
             param['testbenches'] = substitute(filename, fileinfo, template,
 			simvals, maxtime, schemline, localmode, param)
 
-            # For cosimulations, if there is a '.tv' file corresponding to the '.spi' file,
-            # then make substitutions as for the .spi file, and place in characterization
+            # For cosimulations, if there is a '.tv' file corresponding to the '.spice' file,
+            # then make substitutions as for the .spice file, and place in characterization
             # directory.
 
             vtemplate = testbenchpath + '/' + testbench.lower() + '.tv'
@@ -1218,16 +1243,16 @@
 
     return prescore
 
-def check_layout_out_of_date(spipath, layoutpath):
-    # Check if a netlist (spipath) is out-of-date relative to the layouts
+def check_layout_out_of_date(spicepath, layoutpath):
+    # Check if a netlist (spicepath) is out-of-date relative to the layouts
     # (layoutpath).  Need to read the netlist and check all of the subcells.
     need_capture = False
-    if not os.path.isfile(spipath):
+    if not os.path.isfile(spicepath):
         need_capture = True
     elif not os.path.isfile(layoutpath):
         need_capture = True
     else:
-        spi_statbuf = os.stat(spipath)
+        spi_statbuf = os.stat(spicepath)
         lay_statbuf = os.stat(layoutpath)
         if spi_statbuf.st_mtime < lay_statbuf.st_mtime:
             # netlist exists but is out-of-date
@@ -1238,7 +1263,7 @@
             # and check those dates, too.
             layoutdir = os.path.split(layoutpath)[0]
             subrex = re.compile('^[^\*]*[ \t]*.subckt[ \t]+([^ \t]+).*$', re.IGNORECASE)
-            with open(spipath, 'r') as ifile:
+            with open(spicepath, 'r') as ifile:
                 duttext = ifile.read()
             dutlines = duttext.replace('\n+', ' ').splitlines()
             for line in dutlines:
@@ -1256,19 +1281,19 @@
                             break
     return need_capture
 
-def check_schematic_out_of_date(spipath, schempath):
-    # Check if a netlist (spipath) is out-of-date relative to the schematics
+def check_schematic_out_of_date(spicepath, schempath):
+    # Check if a netlist (spicepath) is out-of-date relative to the schematics
     # (schempath).  Need to read the netlist and check all of the subcells.
     need_capture = False
-    if not os.path.isfile(spipath):
+    if not os.path.isfile(spicepath):
         print('Schematic-captured netlist does not exist.  Need to regenerate.')
         need_capture = True
     elif not os.path.isfile(schempath):
         need_capture = True
     else:
-        spi_statbuf = os.stat(spipath)
+        spi_statbuf = os.stat(spicepath)
         sch_statbuf = os.stat(schempath)
-        print('DIAGNOSTIC:  Comparing ' + spipath + ' to ' + schempath)
+        print('DIAGNOSTIC:  Comparing ' + spicepath + ' to ' + schempath)
         if spi_statbuf.st_mtime < sch_statbuf.st_mtime:
             # netlist exists but is out-of-date
             print('Netlist is older than top-level schematic')
@@ -1279,27 +1304,22 @@
             # netlist.  Now need to read the netlist, find all subcircuits,
             # and check those dates, too.
             schemdir = os.path.split(schempath)[0]
+            schrex = re.compile('\*\*[ \t]*sch_path:[ \t]*([^ \t\n]+)', re.IGNORECASE)
             subrex = re.compile('^[^\*]*[ \t]*.subckt[ \t]+([^ \t]+).*$', re.IGNORECASE)
-            with open(spipath, 'r') as ifile:
+            with open(spicepath, 'r') as ifile:
                 duttext = ifile.read()
 
             dutlines = duttext.replace('\n+', ' ').splitlines()
             for line in dutlines:
-                lmatch = subrex.match(line)
+                # xschem helpfully adds a "sch_path" comment line for every subcircuit
+                # coming from a separate schematic file.
+
+                lmatch = schrex.match(line)
                 if lmatch:
-                    subname = lmatch.group(1)
-                    # NOTE: Electric uses library:cell internally to track libraries,
-                    # and maps the ":" to "__" in the netlist.  Not entirely certain that
-                    # the double-underscore uniquely identifies the library:cell. . .
-                    librex = re.compile('(.*)__(.*)', re.IGNORECASE)
-                    lmatch = librex.match(subname)
-                    if lmatch:
-                        elecpath = os.path.split(os.path.split(schempath)[0])[0]
-                        libname = lmatch.group(1)
-                        subschem = elecpath + '/' + libname + '.delib/' + lmatch.group(2) + '.sch'
-                    else:
-                        libname = {}
-                        subschem = schemdir + '/' + subname + '.sch'
+                    subschem = lmatch.group(1)
+                    subfile = os.path.split(subschem)[1]
+                    subname = os.path.splitext(subfile)[0]
+
                     # subcircuits that cannot be found in the current directory are
                     # assumed to be library components or read-only IP components and
                     # therefore never out-of-date.
@@ -1310,31 +1330,6 @@
                             print('Netlist is older than subcircuit schematic ' + subname)
                             need_capture = True
                             break
-                    # mapping of characters to what's allowed in SPICE makes finding
-                    # the associated schematic file a bit difficult.  Requires wild-card
-                    # searching.
-                    elif libname:
-                        restr = lmatch.group(2) + '.sch'
-                        restr = restr.replace('.', '\.')
-                        restr = restr.replace('_', '.')
-                        schrex = re.compile(restr, re.IGNORECASE)
-                        try:
-                            liblist = os.listdir(elecpath + '/' + libname + '.delib')
-                        except FileNotFoundError:
-                            # Potentially could look through the paths in LIBDIR. . .
-                            pass
-                        else:
-                            for file in liblist:
-                                lmatch = schrex.match(file)
-                                if lmatch:
-                                    subschem = elecpath + '/' + libname + '.delib/' + file
-                                    sub_statbuf = os.stat(subschem)
-                                    if spi_statbuf.st_mtime < sch_statbuf.st_mtime:
-                                        # netlist exists but is out-of-date
-                                        need_capture = True
-                                        print('Netlist is older than subcircuit schematic ' + file)
-                                        print('In library ' + libname)
-                                    break
     return need_capture
 
 def printwarn(output):
@@ -1472,17 +1467,17 @@
 
                 versionpath = ipfullpath + '/' + useversion
 
-                # First to do:  Check for /spi-stub entry (which is readable), and
+                # First to do:  Check for /spice/lvs entry (which is readable), and
                 # check if pin order is correct.  Flag a warning if it is not, and
                 # save the pin order in a record so that all X records can be pin
                 # sorted correctly.
 
-                if os.path.exists(versionpath + '/spi-stub'):
-                    stubpath = versionpath + '/spi-stub/' + subname + '/' + subname + '__' + subname + '.spi'
+                if os.path.exists(versionpath + '/spice/lvs'):
+                    lvspath = versionpath + '/spice/lvs/' + subname + '.spice'
                     # More spice file reading!  This should be quick, as these files have
                     # only a single empty subcircuit in them.
                     found = False
-                    with open(stubpath, 'r') as sfile:
+                    with open(lvspath, 'r') as sfile:
                         stubtext = sfile.read()
                         stublines = stubtext.replace('\n+', ' ').replace(',', '|').splitlines()
                         for line in stublines:
@@ -1492,7 +1487,7 @@
                                 stubname = smatch.group(1) 
                                 stublist = smatch.group(2).split()
                                 if stubname != subname + '__' + subname:
-                                    print('Error:  Looking for subcircuit ' + subname + '__' + subname + ' in file ' + stubpath + ' but found subcircuit ' + stubname + ' instead!')
+                                    print('Error:  Looking for subcircuit ' + subname + '__' + subname + ' in file ' + lvspath + ' but found subcircuit ' + stubname + ' instead!')
                                     print("This simulation probably isn't going to go well.")
                                 else:
                                     needsort = False
@@ -1508,26 +1503,26 @@
                                         pinsorts[subname] = pinorder
                                 break
                     if not found:
-                        print('Error:  Cannot find subcircuit in IP spi-stub entry.') 
+                        print('Error:  Cannot find subcircuit in IP spice-stub entry.') 
                 else:
-                    print('Warning: IP has no spi-stub entry, cannot verify pin order.')
+                    print('Warning: IP has no spice-stub entry, cannot verify pin order.')
 
-                if os.path.exists(versionpath + '/spi-rcx'):
+                if os.path.exists(versionpath + '/spice/rcx'):
                     # This path is restricted and can only be seen by ngspice, which is privileged
-                    # to read it.  So we can only assume that it matches the spi-stub entry.
+                    # to read it.  So we can only assume that it matches the spice/stub entry.
                     # NOTE (10/16/2018): Use unexpanded tilde expression in file.
-                    # rcxpath = versionpath + '/spi-rcx/' + subname + '/' + subname + '__' + subname + '.spi'
-                    rcxpath = ippath + '/' + useversion + '/spi-rcx/' + subname + '/' + subname + '__' + subname + '.spi'
+                    # rcxpath = versionpath + '/spice/rcx/' + subname + '/' + subname + '__' + subname + '.spice'
+                    rcxpath = ippath + '/' + useversion + '/spice/rcx/' + subname + '/' + subname + '__' + subname + '.spice'
                     newspilines.append('* Black-box entry replaced by path to RCX netlist')
                     newspilines.append('.include ' + rcxpath)
                     extended_names.append(subname.upper())
-                elif os.path.exists(ipfullpath + '/' + useversion + '/spi'):
-                    # In a pinch, if there is no spi-rcx, try plain spi
+                elif os.path.exists(ipfullpath + '/' + useversion + '/spice'):
+                    # In a pinch, if there is no spice/rcx, try plain spice
                     # NOTE (10/16/2018): Use unexpanded tilde expression in file.
-                    # spipath = versionpath + '/spi/' + subname + '.spi'
-                    spipath = ippath + '/' + useversion + '/spi/' + subname + '.spi'
+                    # spicepath = versionpath + '/spice/' + subname + '.spice'
+                    spicepath = ippath + '/' + useversion + '/spice/' + subname + '.spice'
                     newspilines.append('* Black-box entry replaced by path to schematic netlist')
-                    newspilines.append('.include ' + spipath)
+                    newspilines.append('.include ' + spicepath)
                 else:
                     # Leave as is, and let it force an error
                     newspilines.append(line)
@@ -1550,12 +1545,12 @@
                         # files in spi/!
 
                         newspilines.append('* Need include to schematic netlist for ' + subname)
-                        # However, the CDL stub file can be used to check pin order
-                        stubpath = techdir + '/libs.ref/cdlStub/' + techsubdir + '/stub.cdl'
-                        if os.path.exists(stubpath):
+                        # However, the CDL file can be used to check pin order
+                        cdlpath = techdir + '/libs.ref/' + techsubdir + '/' + techsubdir + '.cdl'
+                        if os.path.exists(cdlpath):
                             # More spice file reading!  This should be quick, as these files have
                             # only a empty subcircuits in them.
-                            with open(stubpath, 'r') as sfile:
+                            with open(cdlpath, 'r') as sfile:
                                 stubtext = sfile.read()
                                 stublines = stubtext.replace('\n+', ' ').replace(',', '|').splitlines()
                                 for line in spilines:
@@ -1580,7 +1575,7 @@
                                         break
 
                         else:
-                            print('No file ' + stubpath + ' found.')
+                            print('No file ' + cdlpath + ' found.')
                             print('Failure to find stub netlist for checking pin order.  Good luck.')
                         break
 
@@ -1605,28 +1600,25 @@
     dname = dsheet['ip-name']
     magpath = dspath + '/mag/'
 
-    spipath = dspath + '/spi/'		# Schematic netlist for sim
-    stubpath = dspath + '/spi/stub/'	# Schematic netlist for LVS
-    pexpath = dspath + '/spi/pex/'	# Layout netlist for sim
-    lvspath = dspath + '/spi/lvs/'	# Layout netlist for LVS
+    spicepath = dspath + '/spice/'	# Schematic netlist for sim
+    pexpath = dspath + '/spice/pex/'	# Layout netlist for sim (C-parasitics)
+    rcxpath = dspath + '/spice/rcx/'	# Layout netlist for sim (R+C-parasitics)
+    lvspath = dspath + '/spice/lvs/'	# Layout netlist for LVS
     vlogpath = dspath + '/verilog/'	# Verilog netlist for sim and LVS
 
-    netlistname = dname + '.spi'
-    schnetlist = spipath + netlistname
-    stubnetlist = stubpath + netlistname
+    netlistname = dname + '.spice'
+    schnetlist = spicepath + netlistname
+    rcxnetlist = rcxpath + netlistname
     pexnetlist = pexpath + netlistname
-    laynetlist = lvspath + netlistname
+    lvsnetlist = lvspath + netlistname
 
     layoutpath = magpath + dname + '.mag'
-    elecpath = dspath + '/elec/' + dname + '.delib'
-    schempath = elecpath + '/' + dname + '.sch'
+    schempath = dspath + '/xschem/' + dname + '.sch'
     verilogpath = vlogpath + dname + '.v'
     pathlast = os.path.split(dspath)[1]
-    verilogaltpath = vlogpath + pathlast + '/' + dname + '.vgl'
     need_sch_capture = True
-    need_stub_capture = True
-    need_lay_capture = True
-    need_pex_capture = True
+    need_extract = True
+    need_pex = True
     force_regenerate = False
 
     # Check if datasheet has been marked for forced netlist regeneration
@@ -1634,20 +1626,8 @@
         if dsheet['regenerate'] == 'force':
             force_regenerate = True
 
-    # If schempath does not exist, check if the .sch file is in a different
-    # library.
     if not os.path.exists(schempath):
         print('No schematic in path ' + schempath)
-        print('Checking for other library paths.')
-        for libname in os.listdir(dspath + '/elec/'):
-            if os.path.splitext(libname)[1] == '.delib':
-                elecpath = dspath + '/elec/' + libname
-                if os.path.exists(elecpath):
-                    for schfile in os.listdir(elecpath):
-                        if schfile == dname + '.sch':
-                            schempath = elecpath + '/' + schfile
-                            print('Schematic found in ' + schempath)
-                            break
 
     # Guess the source based on the file or files in the design directory,
     # with preference given to layout.  This may be overridden in local mode.
@@ -1656,15 +1636,14 @@
         print("Checking for out-of-date netlists.\n")
         netlist_source = dsheet['netlist-source']
         need_sch_capture = check_schematic_out_of_date(schnetlist, schempath)
-        need_stub_capture = check_schematic_out_of_date(stubnetlist, schempath)
         if netlist_source == 'layout':
             netlist_path = pexnetlist
-            need_pex_capture = check_layout_out_of_date(pexnetlist, layoutpath)
-            need_lay_capture = check_layout_out_of_date(laynetlist, layoutpath)
+            need_pex_extract = check_layout_out_of_date(pexnetlist, layoutpath)
+            need_lvs_extract = check_layout_out_of_date(laynetlist, layoutpath)
         else:
             netlist_path = schnetlist
-            need_lay_capture = False
-            need_pex_capture = False
+            need_lvs_extract = False
+            need_pex_extract = False
     else:
         if not localmode:
             print("Remote use, ", end='');
@@ -1675,8 +1654,8 @@
                 netlist_path = pexnetlist
             else:
                 netlist_path = schnetlist
-                need_lay_capture = False
-                need_pex_capture = False
+                need_lvs_extract = False
+                need_pex_extract = False
         else:
             if os.path.exists(layoutpath):
                 netlist_path = pexnetlist
@@ -1684,24 +1663,22 @@
             elif os.path.exists(schempath):
                 netlist_path = schnetlist
                 dsheet['netlist-source'] = 'schematic'
-                need_lay_capture = False
-                need_pex_capture = False
+                need_lvs_extract = False
+                need_pex_extract = False
             elif os.path.exists(verilogpath):
                 netlist_path = verilogpath
                 dsheet['netlist-source'] = 'verilog'
-                need_lay_capture = False
-                need_pex_capture = False
+                need_lvs_extract = False
+                need_pex_extract = False
                 need_sch_capture = False
-                need_stub_capture = False
             elif os.path.exists(verilogaltpath):
                 netlist_path = verilogaltpath
                 dsheet['netlist-source'] = 'verilog'
-                need_lay_capture = False
-                need_pex_capture = False
+                need_lvs_extract = False
+                need_pex_extract = False
                 need_sch_capture = False
-                need_stub_capture = False
 
-    if need_lay_capture or need_pex_capture:
+    if need_lvs_extract or need_pex_extract:
         # Layout LVS netlist needs regenerating.  Check for magic layout.
         if not os.path.isfile(layoutpath):
             print('Error:  No netlist or layout for project ' + dname + '.')
@@ -1717,7 +1694,7 @@
             os.makedirs(pexpath)
 
         print("Extracting LVS netlist from layout. . .")
-        mproc = subprocess.Popen(['/ef/apps/bin/magic', '-dnull', '-noconsole',
+        mproc = subprocess.Popen(['magic', '-dnull', '-noconsole',
 		layoutpath], stdin = subprocess.PIPE, stdout=subprocess.PIPE,
 		stderr=subprocess.STDOUT, cwd = dspath + '/mag',
 		universal_newlines = True)
@@ -1733,11 +1710,11 @@
         # Don't want black box entries, but create them so that we know which
         # subcircuits are in the ip path, then replace them.
         mproc.stdin.write("ext2spice blackbox on\n")
-        if need_lay_capture:
+        if need_lvs_extract:
             mproc.stdin.write("ext2spice cthresh infinite\n")
             mproc.stdin.write("ext2spice rthresh infinite\n")
             mproc.stdin.write("ext2spice -o " + laynetlist + "\n")
-        if need_pex_capture:
+        if need_pex_extract:
             mproc.stdin.write("ext2spice cthresh 0.005\n")
             mproc.stdin.write("ext2spice rthresh 1\n")
             mproc.stdin.write("ext2spice -o " + pexnetlist + "\n")
@@ -1747,20 +1724,20 @@
         if mproc.returncode != 0:
             print('Magic process returned error code ' + str(mproc.returncode) + '\n')
 
-        if need_lay_capture and not os.path.isfile(laynetlist):
+        if need_lvs_extract and not os.path.isfile(laynetlist):
             print('Error:  No LVS netlist extracted from magic.')
-        if need_pex_capture and not os.path.isfile(pexnetlist):
+        if need_pex_extract and not os.path.isfile(pexnetlist):
             print('Error:  No parasitic extracted netlist extracted from magic.')
 
-        if (mproc.returncode != 0) or (need_lay_capture and not os.path.isfile(laynetlist)) or (need_pex_capture and not os.path.isfile(pexnetlist)):
+        if (mproc.returncode != 0) or (need_lvs_extract and not os.path.isfile(laynetlist)) or (need_pex_extract and not os.path.isfile(pexnetlist)):
             return False
 
-        if need_pex_capture and os.path.isfile(pexnetlist):
+        if need_pex_extract and os.path.isfile(pexnetlist):
             print('Generating include statements for read-only IP blocks in layout, if needed')
             layout_netlist_includes(pexnetlist, dspath)
 
-    if need_sch_capture or need_stub_capture:
-        # Netlist needs regenerating.  Check for electric schematic
+    if need_sch_capture:
+        # Netlist needs regenerating.  Check for xschem schematic
         if not os.path.isfile(schempath):
             if os.path.isfile(verilogpath):
                 print('No schematic for project.')
@@ -1776,80 +1753,35 @@
                 print('Error:  No verilog netlist ' + verilogpath + ' or ' + verilogaltpath + ', either.')
                 return False
 
-        # Check if there is a .java directory, if not (e.g., for remote CACE),
-        # then copy it from the defaults.
-        if not os.path.exists(dspath + '/elec/.java'):
-            shutil.copytree('/ef/efabless/deskel/dotjava', dspath + '/elec/.java',
-			symlinks = True)
-
-    # Fix the LIBDIRS file if needed
-    if not os.path.isfile(dspath + '/elec/LIBDIRS'):
-        fix_libdirs(dspath, create = True)
-    elif need_sch_capture or need_stub_capture:
-        fix_libdirs(dspath)
-
     if need_sch_capture:
         print("Generating simulation netlist from schematic. . .")
         # Generate the netlist
-        print('Calling /ef/efabless/bin/elec2spi -o ')
-        libpath = os.path.split(schempath)[0]
-        libname = os.path.split(libpath)[1]
-        print(schnetlist + ' -TS -NTI ' + libname + ' ' + dname + '.sch\n')
+        print('Calling xschem to generate netlist')
 
-        # elec2spi requires that the /spi/ and /spi/stub directory exists
-        if not os.path.exists(spipath):
-            os.makedirs(spipath)
+        if not os.path.exists(spicepath):
+            os.makedirs(spicepath)
 
-        eproc = subprocess.Popen(['/ef/efabless/bin/elec2spi',
-		'-o', schnetlist, '-TS', '-NTI', libname, dname + '.sch'],
+        xproc = subprocess.Popen(['xschem', '-n', '-r', '-q',
+		'--tcl "set top_subckt 1',
+		'-o', schnetlist, dname + '.sch'],
 		stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
-		cwd = dspath + '/elec')
+		cwd = dspath + '/xschem')
 
-        elecout = eproc.communicate()[0]
-        if eproc.returncode != 0:
-            for line in elecout.splitlines():
+        xout = xproc.communicate()[0]
+        if xproc.returncode != 0:
+            for line in xout.splitlines():
                 print(line.decode('utf-8'))
 
-            print('Electric process returned error code ' + str(eproc.returncode) + '\n')
+            print('Xschem process returned error code ' + str(xproc.returncode) + '\n')
         else:
-            printwarn(elecout)
+            printwarn(xout)
 
         if not os.path.isfile(schnetlist):
             print('Error: No netlist found for the circuit!\n')
             print('(schematic netlist for simulation ' + schnetlist + ' not found.)\n')
 
-    if need_stub_capture:
-        print("Generating LVS netlist from schematic. . .")
-        # Generate the netlist
-        print('Calling /ef/efabless/bin/elec2spi -o ')
-        libpath = os.path.split(schempath)[0]
-        libname = os.path.split(libpath)[1]
-        print(stubnetlist + ' -LP -TS -NTI ' + libname + ' ' + dname + '.sch\n')
-
-        # elec2spi requires that the /spi/stub directory exists
-        if not os.path.exists(stubpath):
-            os.makedirs(stubpath)
-
-        eproc = subprocess.Popen(['/ef/efabless/bin/elec2spi',
-		'-o', stubnetlist, '-LP', '-TS', '-NTI', libname, dname + '.sch'],
-		stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
-		cwd = dspath + '/elec')
-
-        elecout = eproc.communicate()[0]
-        if eproc.returncode != 0:
-            for line in elecout.splitlines():
-                print(line.decode('utf-8'))
-
-            print('Electric process returned error code ' + str(eproc.returncode) + '\n')
-        else:
-            printwarn(elecout)
-
-        if not os.path.isfile(stubnetlist):
-            print('Error: No netlist found for the circuit!\n')
-            print('(schematic netlist for LVS ' + stubnetlist + ' not found.)\n')
-
-    if need_sch_capture or need_stub_capture:
-        if (not os.path.isfile(schnetlist)) or (not os.path.isfile(stubnetlist)):
+    if need_sch_capture:
+        if (not os.path.isfile(schnetlist)):
             return False
 
     return netlist_path
@@ -2009,7 +1941,7 @@
                 datasheet_name = 'datasheet.json'
             elif localmode and root_path:
                 # Use normal path to local simulation workspace
-                simulation_path = root_path + '/ngspice/char'
+                simulation_path = root_path + '/ngspice'
 
     # Check that datasheet path exists and that the datasheet is there
     if not os.path.isdir(datasheet_path):
@@ -2032,8 +1964,8 @@
         if 'request-hash' in datatop:
             hashname = datatop['request-hash']
             simulation_path = root_path + '/' + hashname
-        elif os.path.isdir(root_path + '/ngspice/char'):
-            simulation_path = root_path + '/ngspice/char'
+        elif os.path.isdir(root_path + '/ngspice'):
+            simulation_path = root_path + '/ngspice'
         else:
             simulation_path = root_path
     elif not os.path.isabs(simulation_path):
diff --git a/runtime/cace_launch.py b/runtime/cace_launch.py
index ed85b47..5913e49 100755
--- a/runtime/cace_launch.py
+++ b/runtime/cace_launch.py
@@ -31,13 +31,10 @@
 import file_compressor
 import cace_makeplot
 
-import config
-
-# Values imported from config:
-#
-mktp_server_url = config.mktp_server_url
-# obs: og_server_url = config.og_server_url
-simulation_path = config.simulation_path
+# Fix this. . .
+simulation_path = ""
+og_server_url = ""
+mktp_server_url = ""
 
 # Variables needing to be global until this file is properly made into a class
 simfiles_path = []
@@ -91,7 +88,7 @@
         else:
             subprocess.run(['rm', '-r', root_path])
     else:
-        # Remove all .spi files, .data files, .raw files and copy of datasheet
+        # Remove all .spice files, .data files, .raw files and copy of datasheet
         os.chdir(simfiles_path)
         if os.path.exists('datasheet.json'):
             os.remove('datasheet.json')
@@ -102,7 +99,7 @@
             except:
                 pass
             else:
-                if fileext == '.spi' or fileext == '.data' or fileext == '.raw':
+                if fileext == '.spice' or fileext == '.data' or fileext == '.raw':
                     os.remove(filename)
                 elif fileext == '.tv' or fileext == '.tvo' or fileext == '.lxt' or fileext == '.vcd':
                     os.remove(filename)
@@ -491,11 +488,11 @@
     # the original one for backwards compatibility.
     if node == 'XH035':
         node = 'EFXH035A'
-    mag_path = netlist_path + '/lvs/' + ipname + '.spi'
-    schem_path = netlist_path + '/stub/' + ipname + '.spi'
+    mag_path = netlist_path + '/lvs/' + ipname + '.spice'
+    schem_path = netlist_path + ipname + '.spice'
 
     if not os.path.exists(schem_path):
-        schem_path = netlist_path + '/' + ipname + '.spi'
+        schem_path = netlist_path + '/' + ipname + '.spice'
     if not os.path.exists(schem_path):
         if os.path.exists(root_path + '/verilog'):
             schem_path = root_path + '/verilog/' + ipname + '.v'
@@ -527,16 +524,16 @@
         pdkdir = os.path.realpath(root_path + '/.ef-config/techdir')
     else:
         foundry = dsheet['foundry']
-        pdkdir = '/ef/tech/' + foundry + '/' + node
+        pdkdir = '/usr/share/pdk/' + node
     lvs_setup = pdkdir + '/libs.tech/netgen/' + node + '_setup.tcl'
 
     # Run LVS as a subprocess and wait for it to finish.  Use the -json
     # switch to get a file that is easy to parse.
 
-    print('cace_launch.py:  running /ef/apps/bin/netgen -batch lvs ')
+    print('cace_launch.py:  running netgen -batch lvs ')
     print(layout_arg + ' ' + schem_path + ' ' + ipname + ' ' + lvs_setup + ' comp.out -json -blackbox')
 
-    lvsproc = subprocess.run(['/ef/apps/bin/netgen', '-batch', 'lvs',
+    lvsproc = subprocess.run(['netgen', '-batch', 'lvs',
 		layout_arg, schem_path + ' ' + ipname,
 		lvs_setup, 'comp.out', '-json', '-blackbox'], cwd=layout_path,
 		stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)
@@ -1185,7 +1182,7 @@
         if root_path:
             simfiles_path = root_path + '/' + hashname
         else:
-            simfiles_path = config.simulation_path + '/' + hashname
+            simfiles_path = simulation_path + '/' + hashname
 
     if not os.path.isdir(simfiles_path):
         print('Error:  Simulation folder ' + simfiles_path + ' does not exist.')
@@ -1197,10 +1194,10 @@
 
     if not netlist_path:
         if root_path:
-            netlist_path = root_path + '/spi'
+            netlist_path = root_path + '/spice'
 
     # Change location to the simulation directory
-    os.chdir(simfiles_path)
+    # os.chdir(simfiles_path)
 
     # pull out the relevant part of the JSON file, which is "data-sheet"
     dsheet = datatop['data-sheet']
@@ -1322,13 +1319,13 @@
             my_env = os.environ.copy()
             if os.path.exists(verilog):
                 cosim = True
-                simulator = '/ef/apps/bin/vvp'
+                simulator = 'vvp'
                 simargs = ['-M.', '-md_hdl_vpi']
                 filename = verilog + 'o'
                 # Copy the d_hdl object file into the simulation directory
-                shutil.copy('/ef/efabless/lib/iverilog/d_hdl_vpi.vpi', simfiles_path)
+                shutil.copy('d_hdl_vpi.vpi', simfiles_path)
                 # Generate the output executable (.tvo) file for vvp.
-                subprocess.call(['/ef/apps/bin/iverilog', '-o' + filename, verilog])
+                subprocess.call(['iverilog', '-o' + filename, verilog])
                 # Specific version of ngspice must be used for cosimulation
                 # (Deprecated; default version of ngspice now supports cosimulation)
                 # my_env['NGSPICE_VERSION'] = 'cosim1'
@@ -1338,7 +1335,7 @@
                     os.remove('simulator_pipe')
             else:
                 cosim = False
-                simulator = '/ef/apps/bin/ngspice'
+                simulator = 'ngspice'
                 simargs = ['-b']
                 # Do not generate LXT files, as CACE does not have any methods to handle
                 # the data in them anyway.
@@ -1855,7 +1852,7 @@
                 # May want to watch stderr for error messages and/or handle
                 # exit status.
 
-                postproc = subprocess.Popen(['/ef/apps/bin/octave-cli', tb_path],
+                postproc = subprocess.Popen(['octave-cli', tb_path],
 			stdout = subprocess.PIPE)
                 rvalues = postproc.communicate()[0].decode('ascii').splitlines()
 
@@ -2036,9 +2033,9 @@
             if layout_path and netlist_path:
 
                 # Run the device area (area estimation) script
-                if os.path.exists(netlist_path + '/' + ipname + '.spi'):
-                    estproc = subprocess.Popen(['/ef/efabless/bin/layout_estimate.py',
-				netlist_path + '/' + ipname + '.spi', node.lower()],
+                if os.path.exists(netlist_path + '/' + ipname + '.spice'):
+                    estproc = subprocess.Popen(['layout_estimate.py',
+				netlist_path + '/' + ipname + '.spice', node.lower()],
 				stdout=subprocess.PIPE,
 				cwd = layout_path, universal_newlines = True)
                     outlines = estproc.communicate()[0]
@@ -2096,7 +2093,7 @@
                     # script.  Result is either an actual area or an area estimate.
 
                     if os.path.exists(layout_path + '/' + ipname + '.mag'):
-                        areaproc = subprocess.Popen(['/ef/apps/bin/magic',
+                        areaproc = subprocess.Popen(['magic',
 				'-dnull', '-noconsole', layout_path + '/' + ipname + '.mag'],
 				stdin = subprocess.PIPE, stdout = subprocess.PIPE,
 				cwd = layout_path, universal_newlines = True)
@@ -2167,7 +2164,7 @@
                     # Find the layout directory and check if there is a layout
                     # for the cell there.
 
-                    areaproc = subprocess.Popen(['/ef/apps/bin/magic',
+                    areaproc = subprocess.Popen(['magic',
 				'-dnull', '-noconsole', layout_path + '/' + ipname + '.mag'],
 				stdin = subprocess.PIPE, stdout = subprocess.PIPE,
 				cwd = layout_path, universal_newlines = True)
@@ -2207,15 +2204,15 @@
             if not os.path.exists(layout_path):
                 os.makedirs(layout_path)
             if not os.path.exists(layout_path + '/.magicrc'):
-                pdkdir = '/ef/tech/' + foundry + '/' + node + '/libs.tech/magic/current'
+                pdkdir = '/usr/share/pdk/' + node + '/libs.tech/magic'
                 if os.path.exists(pdkdir + '/' + node + '.magicrc'):
                     shutil.copy(pdkdir + '/' + node + '.magicrc', layout_path + '/.magicrc')
 
             # Netlists should have been generated by cace_gensim.py
-            has_layout_nl = os.path.exists(netlist_path + '/lvs/' + ipname + '.spi')
-            has_schem_nl = os.path.exists(netlist_path + '/' + ipname + '.spi')
+            has_layout_nl = os.path.exists(netlist_path + '/lvs/' + ipname + '.spice')
+            has_schem_nl = os.path.exists(netlist_path + '/' + ipname + '.spice')
             has_vlog_nl = os.path.exists(root_path + '/verilog/' + ipname + '.v')
-            has_stub_nl = os.path.exists(netlist_path + '/stub/' + ipname + '.spi')
+            has_stub_nl = os.path.exists(netlist_path + '/stub/' + ipname + '.spice')
             if has_layout_nl and has_stub_nl and not netlist_source == 'schematic':
                 failures = run_and_analyze_lvs(dsheet)
             elif has_layout_nl and has_vlog_nl and not netlist_source == 'schematic':
@@ -2223,23 +2220,23 @@
             elif netlist_path and has_schem_nl:
                 if not has_layout_nl or not has_stub_nl:
                     if not has_layout_nl:
-                        print("Did not find layout LVS netlist " + netlist_path + '/lvs/' + ipname + '.spi')
+                        print("Did not find layout LVS netlist " + netlist_path + '/lvs/' + ipname + '.spice')
                     if not has_stub_nl:
-                        print("Did not find schematic LVS netlist " + netlist_path + '/' + ipname + '.spi')
+                        print("Did not find schematic LVS netlist " + netlist_path + '/' + ipname + '.spice')
                 print("Running layout device pre-check.")
                 if localmode == True:
                     if keepmode == True:
                         precheck_opts = ['-log', '-debug']
                     else:
                         precheck_opts = ['-log']
-                    print('/ef/efabless/bin/layout_precheck.py ' + netlist_path + '/' + ipname + '.spi ' + node.lower() + ' ' + ' '.join(precheck_opts))
-                    chkproc = subprocess.Popen(['/ef/efabless/bin/layout_precheck.py',
-				netlist_path + '/' + ipname + '.spi', node.lower(), *precheck_opts],
+                    print('layout_precheck.py ' + netlist_path + '/' + ipname + '.spice ' + node.lower() + ' ' + ' '.join(precheck_opts))
+                    chkproc = subprocess.Popen(['layout_precheck.py',
+				netlist_path + '/' + ipname + '.spice', node.lower(), *precheck_opts],
 				stdout=subprocess.PIPE,
 				cwd = layout_path, universal_newlines = True)
                 else:
-                    chkproc = subprocess.Popen(['/ef/efabless/bin/layout_precheck.py',
-				netlist_path + '/' + ipname + '.spi', node.lower()],
+                    chkproc = subprocess.Popen(['layout_precheck.py',
+				netlist_path + '/' + ipname + '.spice', node.lower()],
 				stdout=subprocess.PIPE,
 				cwd = layout_path, universal_newlines = True)
                 outlines = chkproc.communicate()[0]
@@ -2307,7 +2304,7 @@
             print('Simulation results retained per -local option\n')
             # If cace_gensim and cace_launch are run locally, keep the results
             # since they won't be posted, but remove all other generated files.
-            os.chdir(simfiles_path)
+            # os.chdir(simfiles_path)
             if os.path.exists('datasheet.json'):
                 os.remove('datasheet.json')
             for filename in filessimmed:
diff --git a/runtime/cace_makeplot.py b/runtime/cace_makeplot.py
index 76eb649..20f35fb 100755
--- a/runtime/cace_makeplot.py
+++ b/runtime/cace_makeplot.py
@@ -8,6 +8,10 @@
 import os
 import matplotlib
 from matplotlib.figure import Figure
+
+# Warning: PIL Tk required, may not be in default install of python3.
+# For Fedora, for example, need "yum install python-pillow-tk"
+
 from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
 from matplotlib.backends.backend_agg import FigureCanvasAgg
 
diff --git a/runtime/file_compressor.py b/runtime/file_compressor.py
new file mode 100755
index 0000000..ef15545
--- /dev/null
+++ b/runtime/file_compressor.py
@@ -0,0 +1,118 @@
+#!/ef/efabless/opengalaxy/venv/bin/python3
+import os
+import re
+from io import BytesIO, BufferedReader, BufferedRandom
+import tarfile
+
+"""
+  This module tars and compresses a filder location and
+  all subdirectories.
+"""
+
+# tar and compress a directory in memory and return the result.
+def tar_directory(source_dir):
+    output = BytesIO()
+    with tarfile.open(fileobj=output, mode='w:gz') as archive:
+        archive.add(source_dir, arcname=os.path.basename(source_dir), recursive=True)
+
+    return output
+
+# tar and compress the files in a directory, not to include the directory itself.
+# 'nodescend' is a list of directories not to descend into, although the directory
+# itself will be added.
+def tar_directory_contents(source_dir, exclude=[]):
+    output = BytesIO()
+    curdir = os.getcwd()
+    os.chdir(source_dir)
+
+    rexclude = []
+    for pattern in exclude:
+        rexclude.append(re.compile(pattern))
+
+    with tarfile.open(fileobj=output, mode='w:gz') as archive:
+        for root, dirs, files in os.walk('.'):
+            for filename in files:
+                if root == '.':
+                    filepath = filename
+                else:
+                    rootnodot = os.path.normpath(root)
+                    filepath = os.path.join(rootnodot, filename)
+                doexclude = False
+                for regexp in rexclude:
+                    if re.match(regexp, filepath):
+                        doexclude = True
+                        break
+                if not doexclude:
+                    try:
+                        archive.add(filepath, recursive=False)
+                    except PermissionError:
+                        pass
+            for dirname in dirs[:]:
+                if root == '.':
+                    dirpath = dirname
+                else:
+                    rootnodot = os.path.normpath(root)
+                    dirpath = os.path.join(rootnodot, dirname)
+                doexclude = False
+                for regexp in rexclude:
+                    if re.match(regexp, dirpath):
+                        doexclude = True
+                        break
+                if doexclude:
+                    dirs.remove(dirname)
+                else:
+                    try:
+                        archive.add(dirpath, recursive=False)
+                    except PermissionError:
+                        pass
+
+
+    os.chdir(curdir)
+    return output
+
+def tar_directory_contents_to_file(source_dir, tarballname, exclude=[]):
+    curdir = os.getcwd()
+    os.chdir(source_dir)
+
+    rexclude = []
+    for pattern in exclude:
+        rexclude.append(re.compile(pattern))
+
+    with tarfile.open(tarballname, mode='w:gz') as archive:
+        for root, dirs, files in os.walk('.'):
+            for filename in files:
+                if root == '.':
+                    filepath = filename
+                else:
+                    rootnodot = os.path.normpath(root)
+                    filepath = os.path.join(rootnodot, filename)
+                doexclude = False
+                for regexp in rexclude:
+                    if re.match(regexp, filepath):
+                        doexclude = True
+                        break
+                if not doexclude:
+                    try:
+                        archive.add(filepath, recursive=False)
+                    except PermissionError:
+                        pass
+            for dirname in dirs[:]:
+                if root == '.':
+                    dirpath = dirname
+                else:
+                    rootnodot = os.path.normpath(root)
+                    dirpath = os.path.join(rootnodot, dirname)
+                doexclude = False
+                for regexp in rexclude:
+                    if re.match(regexp, dirpath):
+                        doexclude = True
+                        break
+                if doexclude:
+                    dirs.remove(dirname)
+                else:
+                    try:
+                        archive.add(dirpath, recursive=False)
+                    except PermissionError:
+                        pass
+
+    os.chdir(curdir)
diff --git a/runtime/project_manager.py b/runtime/project_manager.py
index 2be5f58..247f55c 100755
--- a/runtime/project_manager.py
+++ b/runtime/project_manager.py
@@ -81,7 +81,8 @@
 except:
     pdk_root = 'PREFIX/pdk'
 
-apps_path = pdk_root + '/scripts'
+# Application path (path where this script is located)
+apps_path = os.path.realpath(os.path.dirname(__file__))
 
 #---------------------------------------------------------------
 # Watch a directory for modified time change.  Repeat every two
@@ -223,7 +224,7 @@
         self.nentry.insert(0, seed or '')      # may be None
         self.pvar = tkinter.StringVar(master)
         if not importnode:
-            # Add PDKs as found by searching /ef/tech for 'libs.tech' directories
+            # Add PDKs as found by searching /usr/share/pdk for 'libs.tech' directories
             ttk.Label(master, text="Select foundry/node:").grid(row = 2, column = 0)
         else:
             ttk.Label(master, text="Foundry/node:").grid(row = 2, column = 0)
@@ -239,9 +240,6 @@
             node_def = "EFXH035B"
 
         # use glob instead of os.walk. Don't need to recurse large PDK hier.
-        # TODO: stop hardwired default EFXH035B: get from an overall flow /ef/tech/.ef-config/plist.json
-        # (or get it from the currently selected project)
-        #EFABLESS PLATFORM
         for pdkdir_lr in glob.glob(pdk_root + '/*/libs.tech/'):
             pdkdir = os.path.split( os.path.split( pdkdir_lr )[0])[0]    # discard final .../libs.tech/
             (foundry, foundry_name, node, desc, status) = ProjectManager.pdkdir2fnd( pdkdir )
@@ -254,8 +252,6 @@
             if node == node_def and not pdk_def:
                 pdk_def = key
             
-        # Quick hack:  sorting puts EFXH035A before EFXH035LEGACY.  However, some
-        # ranking is needed.
         pdklist = sorted( self.pdkmap.keys())
         if not pdklist:
             raise ValueError( "assertion failed, no available PDKs found")
@@ -656,7 +652,7 @@
             self.error_label.configure(text = 'Cannot import a parent directory into itself.')
             return False
         #Find project pdk
-        if os.path.exists(self.projectpath + '/.config/techdir') or os.path.exists(self.projectpath + '/.ef-config/techdir'):
+        if os.path.exists(self.projectpath + '/.config/techdir') or os.path.exists(self.projectpath + '/.config/techdir'):
             self.project_pdkdir = os.path.realpath(self.projectpath + ProjectManager.config_path( self.projectpath) + '/techdir')
             self.foundry, foundry_name, self.node, desc, status = ProjectManager.pdkdir2fnd( self.project_pdkdir )
         else:
@@ -1181,12 +1177,10 @@
     
     @classmethod
     def config_path(cls, path):
-        #returns the config directory that 'path' contains between .config and .ef-config
+        #returns the config directory that 'path' contains (.config)
         if (os.path.exists(path + '/.config')):
             return '/.config'
-        elif (os.path.exists(path + '/.ef-config')):
-            return '/.ef-config'
-        raise Exception('Neither '+path+'/.config nor '+path+'/.ef-config exists.')
+        raise Exception(' '+path+'/.config does not exist.')
 
     #------------------------------------------------------------------------
     # Check if a name is blacklisted for being a project folder
@@ -1231,8 +1225,7 @@
             
             # 
             #EFABLESS PLATFORM
-            p = subprocess.run(['/ef/apps/bin/withnet' ,
-			apps_path + '/og_uid_service.py', userid],
+            p = subprocess.run([apps_path + '/og_uid_service.py', userid],
 			stdout = subprocess.PIPE)
             if p.stdout:
                 uid_string = p.stdout.splitlines()[0].decode('utf-8')
@@ -1362,60 +1355,6 @@
         return projectlist
 
     #------------------------------------------------------------------------
-    # utility: [re]intialize a project's elec/ dir: the .java preferences and LIBDIRS.
-    # So user can just delete .java, and restart electric (from projectManager), to reinit preferences.
-    # So user can just delete LIBDIRS, and restart electric (from projectManager), to reinit LIBDIRS.
-    # So project copies/imports can filter ngspice/run (and ../.allwaves), we'll recreate it here.
-    #
-    # The global /ef/efabless/deskel/* is used and the PDK name substituted.
-    #
-    # This SINGLE function is used to setup elec/ contents for new projects, in addition to being
-    # called in-line prior to "Edit Schematics" (on-the-fly).
-    #------------------------------------------------------------------------
-    @classmethod
-    def reinitElec(cls, design):
-        pdkdir = os.path.join( design, ".ef-config/techdir")
-        elec = os.path.join( design, "elec")
-
-        # on the fly, ensure has elec/ dir, ensure has ngspice/run/allwaves dir
-        try:
-            os.makedirs(design + '/elec', exist_ok=True)
-        except IOError as e:
-            print('Error in os.makedirs(elec): ' + str(e))
-        try:
-            os.makedirs(design + '/ngspice/run/.allwaves', exist_ok=True)
-        except IOError as e:
-            print('Error in os.makedirs(.../.allwaves): ' + str(e))
-        #EFABLESS PLATFORM
-        deskel = '/ef/efabless/deskel'
-        
-        # on the fly:
-        # .../elec/.java : reinstall if missing. From PDK-specific if any.
-        if not os.path.exists( os.path.join( elec, '.java')):
-            # Copy Electric preferences
-            try:
-                shutil.copytree(deskel + '/dotjava', design + '/elec/.java', symlinks = True)
-            except IOError as e:
-                print('Error copying files: ' + str(e))
-
-        # .../elec/LIBDIRS : reinstall if missing, from PDK-specific LIBDIRS
-        # in libs.tech/elec/LIBDIRS
-
-        libdirsloc = pdkdir + '/libs.tech/elec/LIBDIRS'
-
-        if not os.path.exists( os.path.join( elec, 'LIBDIRS')):
-            if os.path.exists( libdirsloc ):
-                # Copy Electric LIBDIRS
-                try:
-                    shutil.copy(libdirsloc, design + '/elec/LIBDIRS')
-                except IOError as e:
-                    print('Error copying files: ' + str(e))
-            else:
-                print('Info: PDK not configured for Electric: no libs.tech/elec/LIBDIRS')
-
-        return None
-
-    #------------------------------------------------------------------------
     # utility: filter a list removing: empty strings, strings with any whitespace
     #------------------------------------------------------------------------
     whitespaceREX = re.compile('\s')
@@ -1444,7 +1383,7 @@
     # is always ''. And an optional foundry extension is pruned/dropped.
     # thus '.../XFAB.2/EFXP018A4' -> 'XFAB', 'EFXP018A4', ''
     #
-    # optionally store in each PDK: .ef-config/nodeinfo.json which can define keys:
+    # optionally store in each PDK: .config/nodeinfo.json which can define keys:
     # 'foundry', 'node', 'description' to override the foundry (computed from the path)
     # and (fixed, empty) description currently returned by this.
     #
@@ -1488,7 +1427,7 @@
                     status = nodeinfo['status']
                 return foundry, foundry_name, node, description, status
             
-            infofile = pdkdir + '/.ef-config/nodeinfo.json'
+            infofile = pdkdir + '/.config/nodeinfo.json'
             if os.path.exists(infofile):
                 with open(infofile, 'r') as ifile:
                     nodeinfo = json.load(ifile)
@@ -1541,11 +1480,11 @@
     # Get the PDK attached to a project for display as: '<foundry> : <node>'
     # unless path=True: then return true PDK dir-path.
     #
-    # TODO: the ef-config prog output is not used below. Intent was use
-    # ef-config to be the one official query for *any* project's PDK value, and
+    # TODO: the config prog output is not used below. Intent was use
+    # config to be the one official query for *any* project's PDK value, and
     # therein-only hide a built-in default for legacy projects without techdir symlink.
-    # In below ef-config will always give an EF_TECHDIR, so that code-branch always
-    # says '(default)', the ef-config subproc is wasted, and '(no PDK)' is never
+    # In below config will always give an EF_TECHDIR, so that code-branch always
+    # says '(default)', the config subproc is wasted, and '(no PDK)' is never
     # reached.
     #------------------------------------------------------------------------
     def get_pdk_dir(self, project, path=False):
@@ -1555,9 +1494,9 @@
         foundry, foundry_name, node, desc, status = self.pdkdir2fnd( pdkdir )
         return foundry + ' : ' + node
         '''
-        if os.path.isdir(project + '/.ef-config'):
-            if os.path.exists(project + '/.ef-config/techdir'):
-                pdkdir = os.path.realpath(project + '/.ef-config/techdir')
+        if os.path.isdir(project + '/.config'):
+            if os.path.exists(project + '/.config/techdir'):
+                pdkdir = os.path.realpath(project + '/.config/techdir')
                 
         elif os.path.isdir(project + '/.config'):
             if os.path.exists(project + '/.config/techdir'):
@@ -1569,10 +1508,10 @@
         '''
         '''
         if not pdkdir:
-            # Run "ef-config" script for backward compatibility
+            # Run "config" script for backward compatibility
             export = {'EF_DESIGNDIR': project}
             #EFABLESS PLATFORM
-            p = subprocess.run(['/ef/efabless/bin/ef-config', '-sh', '-t'],
+            p = subprocess.run(['config', '-sh', '-t'],
 			stdout = subprocess.PIPE, env = export)
             config_out = p.stdout.splitlines()
             for line in config_out:
@@ -1823,8 +1762,6 @@
         if not os.path.exists(newproject + '/mag'):
             os.makedirs(newproject + '/mag')
 
-        self.reinitElec(newproject)   # [re]install elec/.java, elec/LIBDIRS if needed, from pdk-specific if-any
-
         return 1	# Success
 
     #------------------------------------------------------------------------
@@ -1856,7 +1793,7 @@
                 os.makedirs(newproject + '/cdl/')
             shutil.copy(importfile, newproject + '/cdl/' + newfile)
             try:
-                p = subprocess.run(['/ef/apps/bin/cdl2spi', importfile],
+                p = subprocess.run(['cdl2spi', importfile],
 			stdout = subprocess.PIPE, stderr = subprocess.PIPE,
 			check = True)
             except subprocess.CalledProcessError as e:
@@ -1906,7 +1843,7 @@
 
         # Run cdl2icon perl script
         try:
-            p = subprocess.run(['/ef/apps/bin/cdl2icon', '-file', importfile, '-cellname',
+            p = subprocess.run(['cdl2icon', '-file', importfile, '-cellname',
 			subname, '-libname', pname, '-projname', pname, '--prntgussddirs'],
 			stdout = subprocess.PIPE, stderr = subprocess.PIPE, check = True)
         except subprocess.CalledProcessError as e:
@@ -1940,7 +1877,7 @@
         # Call cdl2icon with the final pin directions
         outname = newproject + '/elec/' + pname + '.delib/' + os.path.splitext(newfile)[0] + '.ic'
         try:
-            p = subprocess.run(['/ef/apps/bin/cdl2icon', '-file', importfile, '-cellname',
+            p = subprocess.run(['cdl2icon', '-file', importfile, '-cellname',
 			subname, '-libname', pname, '-projname', pname, '-output',
 			outname, '-pindircmbndstring', ','.join(pin_info_list)],
 			stdout = subprocess.PIPE, stderr = subprocess.PIPE, check = True)
@@ -2446,7 +2383,7 @@
 				+ parentdir + "/docs/.\n")
 
             # Get the names of verilog libraries in this PDK.
-            pdkdir = os.path.realpath(ppath + '/.ef-config/techdir')
+            pdkdir = os.path.realpath(ppath + '/.config/techdir')
             pdkvlog = pdkdir + '/libs.ref/verilog'
             pdkvlogfiles = glob.glob(pdkvlog + '/*/*.v')
 
@@ -2746,7 +2683,7 @@
 
         newproject = self.projectdir + '/' + pname
         try:
-            p = subprocess.run(['/ef/apps/bin/vglImport', importfile, pname, elecLib],
+            p = subprocess.run(['vglImport', importfile, pname, elecLib],
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                                check=True, universal_newlines=True)
         except subprocess.CalledProcessError as e:
@@ -2935,7 +2872,7 @@
         if not confirm == 'okay':
             print('Warning: Must quit and restart to get any fixes or updates.')
             return
-        os.execl('/ef/efabless/opengalaxy/project_manager.py', 'appsel_zenity.sh')
+        os.execl('project_manager.py', 'appsel_zenity.sh')
         # Does not return; replaces existing process.
 
     #----------------------------------------------------------------------
@@ -3146,33 +3083,16 @@
             os.makedirs(newproject + '/testbench')
             os.makedirs(newproject + '/verilog')
             os.makedirs(newproject + '/verilog/source')
-            os.makedirs(newproject + '/.ef-config')
+            os.makedirs(newproject + '/.config')
             if 'xschem' in schemapps:
                 os.makedirs(newproject + '/xschem')
 
             pdkname = os.path.split(newpdk)[1]
 
             # Symbolic links
-            os.symlink(newpdk, newproject + '/.ef-config/techdir')
-
-            # Copy preferences
-            # deskel = '/ef/efabless/deskel'
-            #
-            # Copy examples (disabled;  this is too confusing to the end user.  Also, they
-            # should not be in user space at all, as they are not user editable.
-            #
-            # for item in os.listdir(deskel + '/exlibs'):
-            #     shutil.copytree(deskel + '/exlibs/' + item, newproject + '/elec/' + item)
-            # for item in os.listdir(deskel + '/exmag'):
-            #     if os.path.splitext(item)[1] == '.mag':
-            #         shutil.copy(deskel + '/exmag/' + item, newproject + '/mag/' + item)
+            os.symlink(newpdk, newproject + '/.config/techdir')
 
             # Put tool-specific startup files into the appropriate user directories.
-            if 'electric' in layoutapps or 'electric' in schemapps:
-                self.reinitElec(newproject)   # [re]install elec/.java, elec/LIBDIRS if needed, from pdk-specific if-any
-                # Set up electric
-                self.create_electric_header_file(newproject, newname)
-
             if 'magic' in layoutapps:
                 shutil.copy(newpdk + '/libs.tech/magic/' + pdkname + '.magicrc', newproject + '/mag/.magicrc')
 
@@ -3415,17 +3335,8 @@
                 os.makedirs(newproject + '/ngspice/run/.allwaves')
             except FileExistsError:
                 pass
-        '''
-        if not elprefs:
-            # Copy preferences
-            deskel = '/ef/efabless/deskel'
-            try:
-                shutil.copytree(deskel + '/dotjava', newproject + '/elec/.java', symlinks = True)
-            except IOError as e:
-                print('Error copying files: ' + e)
-        '''
 
-#----------------------------------------------------------------------
+    #----------------------------------------------------------------------
     # Allow the user to choose the flow of the project
     #----------------------------------------------------------------------
     
@@ -3594,7 +3505,7 @@
         
         def make_techdirs(projectpath, project_pdkdir):
             # Recursively create techdirs in project and subproject folders
-            if not (os.path.exists(projectpath + '/.config') or os.path.exists(projectpath + '/.ef-config')):
+            if not (os.path.exists(projectpath + '/.config') or os.path.exists(projectpath + '/.config')):
                 os.makedirs(projectpath + '/.config')
             if not os.path.exists(projectpath + self.config_path(projectpath) + '/techdir'):
                 os.symlink(project_pdkdir, projectpath + self.config_path(projectpath) + '/techdir')
@@ -3936,7 +3847,7 @@
 
         export = dict(os.environ)
         export['EF_DESIGNDIR'] = ppath
-        subprocess.Popen(['/ef/apps/bin/padframe-calc', elecLib, cellname], cwd = ppath, env = export)
+        subprocess.Popen(['padframe-calc', elecLib, cellname], cwd = ppath, env = export)
 
         # not yet any useful return value or reporting of results here in projectManager...
         return 1
@@ -3994,21 +3905,7 @@
             libs = []
             ellibrex = re.compile(r'^(tech_.*|ef_examples)\.[dj]elib$', re.IGNORECASE)
 
-            self.reinitElec(design)
-
-            # /elec and /.java are prerequisites for running electric
-            if not os.path.exists(design + '/elec'):
-                print("No path to electric design folder.")
-                return
-
-            if not os.path.exists(design + '/elec/.java'):
-                print("No path to electric .java folder.")
-                return
-
-            # Fix the LIBDIRS file if needed
-            #fix_libdirs(design, create = True)
-
-            # Check for legacy directory (missing .ef-config and/or .ef-config/techdir);
+            # Check for legacy directory (missing .config and/or .config/techdir);
             # Handle as necessary.
 
             # don't sometimes yield pdkdir as some subdir of techdir
@@ -4017,7 +3914,7 @@
                 export = dict(os.environ)
                 export['EF_DESIGNDIR'] = design
                 '''
-                p = subprocess.run(['/ef/efabless/bin/ef-config', '-sh', '-t'],
+                p = subprocess.run(['config', '-sh', '-t'],
 			stdout = subprocess.PIPE, env = export)
                 config_out = p.stdout.splitlines()
                 for line in config_out:
@@ -4084,7 +3981,7 @@
             if indirectlibs:
                 export['EOPENARGS'] = ' '.join(indirectlibs)
                 arguments.append('-s')
-                arguments.append('/ef/efabless/lib/elec/elecOpen.bsh')
+                arguments.append('elecOpen.bsh')
 
             try:
                 arguments.append(libs[-1])
@@ -4213,9 +4110,9 @@
             pdkdir = ''
             pdkname = ''
             
-            if os.path.exists(design + '/.ef-config/techdir/libs.tech'):
-                pdkdir = design + '/.ef-config/techdir/libs.tech/magic/current'
-                pdkname = os.path.split(os.path.realpath(design + '/.ef-config/techdir'))[1]
+            if os.path.exists(design + '/.config/techdir/libs.tech'):
+                pdkdir = design + '/.config/techdir/libs.tech/magic/current'
+                pdkname = os.path.split(os.path.realpath(design + '/.config/techdir'))[1]
             elif os.path.exists(design + '/.config/techdir/libs.tech'):
                 pdkdir = design + '/.config/techdir/libs.tech/magic'
                 pdkname = os.path.split(os.path.realpath(design + '/.config/techdir'))[1]
@@ -4287,9 +4184,9 @@
                     # NOTE:  netlist_to_layout script will attempt to generate a
                     # schematic netlist if one does not exist.
 
-                    print('Running /ef/efabless/bin/netlist_to_layout.py ../spi/' + designname + '.spi')
+                    print('Running netlist_to_layout.py ../spi/' + designname + '.spi')
                     try:
-                        p = subprocess.run(['/ef/efabless/bin/netlist_to_layout.py',
+                        p = subprocess.run(['netlist_to_layout.py',
 					'../spi/' + designname + '.spi'],
 					stdin = subprocess.PIPE, stdout = subprocess.PIPE,
 					stderr = subprocess.PIPE, cwd = design + '/mag')
@@ -4303,7 +4200,7 @@
                     else:
                         if os.path.exists(design + '/mag/create_script.tcl'):
                             with open(design + '/mag/create_script.tcl', 'r') as infile:
-                                magproc = subprocess.run(['/ef/apps/bin/magic',
+                                magproc = subprocess.run(['magic',
 					'-dnull', '-noconsole', '-rcfile ',
 					pdkdir + '/' + pdkname + '.magicrc', designname],
 					stdin = infile, stdout = subprocess.PIPE,
@@ -4356,7 +4253,6 @@
     #----------------------------------------------------------------------
 
     def upload(self):
-        '''
         global apps_path
         value = self.projectselect.selected()
         if value:
@@ -4364,10 +4260,8 @@
             # designname = value['text']
             designname = self.project_name
             print('Upload design ' + designname + ' (' + design + ' )')
-            subprocess.run(['/ef/apps/bin/withnet',
-			apps_path + '/cace_design_upload.py',
+            subprocess.run([apps_path + '/cace_design_upload.py',
 			design, '-test'])
-	'''
 
     #--------------------------------------------------------------------------
 
@@ -4398,8 +4292,8 @@
         
         if os.path.exists(svalues[0] + '/.config'):
             pdkdir = svalues[0] + '/.config/techdir'
-        elif os.path.exists(svalues[0] + '/.ef-config'):
-            pdkdir = svalues[0] + '/.ef-config/techdir'
+        elif os.path.exists(svalues[0] + '/.config'):
+            pdkdir = svalues[0] + '/.config/techdir'
             ef_style=True
         
         if pdkdir == '':
diff --git a/runtime/spiceunits.py b/runtime/spiceunits.py
new file mode 100755
index 0000000..6cd45ba
--- /dev/null
+++ b/runtime/spiceunits.py
@@ -0,0 +1,209 @@
+#!/ef/efabless/opengalaxy/venv/bin/python3
+"""spice_units.py: Converts tuple of (unit, value) into standard unit numeric value."""
+
+import re
+
+# set of metric prefixes and the value needed to multiply by to
+# get the "standard" unit for SPICE.  Only standard units will
+# be written into the SPICE file, for reasons of universal
+# compatibility.
+
+prefixtypes = {
+	"T": 1E12,  "tera" : 1E12,
+	"G": 1E9,   "giga" : 1E9,
+	"M": 1E6,   "mega" : 1E6,   "MEG": 1E6, "meg": 1E6,
+	"K": 1E3,   "kilo" : 1E3,   "k":1E3,
+	"D": 1E1,   "deca" : 1E1,
+	"d": 1E-1,  "deci" : 1E-1,
+	"c": 1E-2,  "centi": 1E-2,  "%": 1E-2,
+	"m": 1E-3,  "milli": 1E-3,
+	"u": 1E-6,  "micro": 1E-6,  "\u00b5": 1E-6, "ppm": 1E-6,
+	"n": 1E-9,  "nano" : 1E-9,  "ppb": 1E-9,
+	"p": 1E-12, "pico" : 1E-12, "ppt": 1E-12,
+	"f": 1E-15, "femto": 1E-15,
+	"a": 1E-18, "atto" : 1E-15,
+}
+
+# set of known unit types, including some with suffixes, along with a
+# keyword that can be used to limit the search if an expected type for
+# the value is known.  Keys are used in regular expressions, and so
+# may use any regular expression syntax.
+
+unittypes = {
+	"[Ff]": "capacitance",
+	"[Ff]arad[s]*": "capacitance",
+	"\u03a9": "resistance", 
+	"[Oo]hm[s]*": "resistance",
+	"[Vv]": "voltage",
+	"[Vv]olt[s]*": "voltage",
+	"[Aa]": "current",
+	"[Aa]mp[s]*": "current",
+	"[Aa]mpere[s]*": "current",
+	"[Ss]": "time",
+	"[Ss]econd[s]*": "time",
+	"[Hh]": "inductance",
+	"[Hh]enry[s]*": "inductance",
+	"[Hh]enries": "inductance",
+	"[Hh]z": "frequency",
+	"[Hh]ertz": "frequency",
+	"[Mm]": "distance",
+	"[Mm]eter[s]*": "distance",
+	"[\u00b0]*[Cc]": "temperature",
+	"[\u00b0]*[Cc]elsius": "temperature",
+	"[\u00b0]*[Kk]": "temperature",
+	"[\u00b0]*[Kk]elvin": "temperature",
+	"[Ww]": "power",
+	"[Ww]att[s]*": "power",
+	"[Vv]-rms": "noise",
+	"[Vv]olt[s]*-rms": "noise",
+	"'[bohd]": "digital",
+	"": "none"
+}
+
+# Define how to convert SI units to spice values
+#
+# NOTE: spice_unit_unconvert can act on a tuple of (units, value) where
+# value is either a single value or a list of values.  spice_unit_convert
+# only acts on a tuple with a single value.  This is because the only large
+# vectors are produced by ngspice, and these values need unconverting back
+# into the units specified by the datasheet.  Values being converted to
+# ngspice units are from the datasheet and are only computed a few at a
+# time, so handling vectors is not particularly efficient.
+
+def spice_unit_convert(valuet, restrict=[]):
+    """Convert SI units into spice values"""
+    # valuet is a tuple of (unit, value), where "value" is numeric
+    # and "unit" is a string.  "restrict" may be used to require that
+    # the value be of a specific class like "time" or "resistance". 
+
+    # Recursive handling of '/' and multiplicatioon dot in expressions
+    if '/' in valuet[0]:
+        parts = valuet[0].split('/', 1)
+        result = float(spice_unit_convert([parts[0], valuet[1]], restrict))
+        result /= float(spice_unit_convert([parts[1], "1.0"], restrict))
+        return str(result)
+
+    if '\u22c5' in valuet[0]:	# multiplication dot
+        parts = valuet[0].split('\u22c5')
+        result = float(spice_unit_convert([parts[0], valuet[1]], restrict))
+        result *= float(spice_unit_convert([parts[1], "1.0"], restrict))
+        return str(result)
+
+    if '\u00b2' in valuet[0]:	# squared
+        part = valuet[0].split('\u00b2')[0]
+        result = float(spice_unit_unconvert([part, valuet[1]], restrict))
+        result *= float(spice_unit_unconvert([part, "1.0"], restrict))
+        return str(result)
+
+    if valuet[0] == "":		# null case, no units
+        return valuet[1]
+
+    for unitrec in unittypes:	# case of no prefix
+        if re.match('^' + unitrec + '$', valuet[0]):
+            if restrict:
+                if unittypes[unitrec] == restrict.lower():
+                    return valuet[1]
+            else:
+                return valuet[1]
+
+    for prerec in prefixtypes:
+        for unitrec in unittypes:
+            if re.match('^' + prerec + unitrec + '$', valuet[0]):
+                if restrict:
+                    if unittypes[unitrec] == restrict.lower():
+                        newvalue = float(valuet[1]) * prefixtypes[prerec]
+                        return str(newvalue)
+                else:
+                    newvalue = float(valuet[1]) * prefixtypes[prerec]
+                    return str(newvalue)
+
+    # Check for "%", which can apply to anything.
+    if valuet[0][0] == '%':
+        newvalue = float(valuet[1]) * 0.01
+        return str(newvalue)
+    
+    if restrict:
+        raise ValueError('units ' + valuet[0] + ' cannot be parsed as ' + restrict.lower())
+    else:
+        # raise ValueError('units ' + valuet[0] + ' cannot be parsed')
+        # (Assume value is not in SI units and will be passed back as-is)
+        return valuet[1]
+
+# Define how to convert spice values back into SI units
+
+def spice_unit_unconvert(valuet, restrict=[]):
+    """Convert spice values back into SI units"""
+    # valuet is a tuple of (unit, value), where "value" is numeric
+    # and "unit" is a string.  "restrict" may be used to require that
+    # the value be of a specific class like "time" or "resistance". 
+
+    # Recursive handling of '/' and multiplicatioon dot in expressions
+    if '/' in valuet[0]:
+        parts = valuet[0].split('/', 1)
+        result = spice_unit_unconvert([parts[0], valuet[1]], restrict)
+        if isinstance(result, list):
+            result = list(item / spice_unit_unconvert([parts[1], 1.0],
+			restrict) for item in result)
+        else:
+            result /= spice_unit_unconvert([parts[1], 1.0], restrict)
+        return result
+
+    if '\u22c5' in valuet[0]:	# multiplication dot
+        parts = valuet[0].split('\u22c5')
+        result = spice_unit_unconvert([parts[0], valuet[1]], restrict)
+        if isinstance(result, list):
+            result = list(item * spice_unit_unconvert([parts[1], 1.0],
+			restrict) for item in result)
+        else:
+            result *= spice_unit_unconvert([parts[1], 1.0], restrict)
+        return result
+
+    if '\u00b2' in valuet[0]:	# squared
+        part = valuet[0].split('\u00b2')[0]
+        result = spice_unit_unconvert([part, valuet[1]], restrict)
+        if isinstance(result, list):
+            result = list(item * spice_unit_unconvert([part, 1.0],
+			restrict) for item in result)
+        else:
+            result *= spice_unit_unconvert([part, 1.0], restrict)
+        return result
+
+    if valuet[0] == "":		# null case, no units
+        return valuet[1]
+
+    for unitrec in unittypes:	# case of no prefix
+        if re.match('^' + unitrec + '$', valuet[0]):
+            if restrict:
+                if unittypes[unitrec] == restrict.lower():
+                    return valuet[1]
+            else:
+                return valuet[1]
+
+    for prerec in prefixtypes:
+        for unitrec in unittypes:
+            if re.match('^' + prerec + unitrec + '$', valuet[0]):
+                if restrict:
+                    if unittypes[unitrec] == restrict.lower():
+                        if isinstance(valuet[1], list):
+                            return list(item / prefixtypes[prerec] for item in valuet[1])
+                        else:
+                            return valuet[1] / prefixtypes[prerec]
+                else:
+                    if isinstance(valuet[1], list):
+                        return list(item / prefixtypes[prerec] for item in valuet[1])
+                    else:
+                        return valuet[1] / prefixtypes[prerec]
+
+    # Check for "%", which can apply to anything.
+    if valuet[0][0] == '%':
+        if isinstance(valuet[1], list):
+            return list(item * 100 for item in valuet[1])
+        else:
+            return valuet[1] * 100
+    
+    if restrict:
+        raise ValueError('units ' + valuet[0] + ' cannot be parsed as ' + restrict.lower())
+    else:
+        # raise ValueError('units ' + valuet[0] + ' cannot be parsed')
+        # (Assume value is not in SI units and will be passed back as-is)
+        return valuet[1]
diff --git a/runtime/textreport.py b/runtime/textreport.py
new file mode 100644
index 0000000..f44e09c
--- /dev/null
+++ b/runtime/textreport.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+#
+#--------------------------------------------------------
+# Text Report Window for the Open Galaxy characterization
+# tool (simple text window with contents read from file)
+#
+#--------------------------------------------------------
+# Written by Tim Edwards
+# efabless, inc.
+# June 27, 2017
+# Version 0.1
+#--------------------------------------------------------
+
+import os
+import re
+import tkinter
+from tkinter import ttk
+
+class TextReport(tkinter.Toplevel):
+    """Open Galaxy text report window."""
+
+    def __init__(self, parent=None, fontsize = 11, *args, **kwargs):
+        '''See the __init__ for Tkinter.Toplevel.'''
+        tkinter.Toplevel.__init__(self, parent, *args, **kwargs)
+
+        s = ttk.Style()
+        s.configure('normal.TButton', font=('Helvetica', fontsize), border = 3, relief = 'raised')
+        self.protocol("WM_DELETE_WINDOW", self.close)
+
+        self.withdraw()
+        self.title('Open Galaxy Text Report')
+
+        self.texttitle = ttk.Label(self, style='title.TLabel', text = '(no text)')
+        self.texttitle.grid(column = 0, row = 0, sticky = "news")
+        self.textbar = ttk.Separator(self, orient='horizontal')
+        self.textbar.grid(column = 0, row = 1, sticky = "news")
+
+        self.hframe = tkinter.Frame(self)
+        self.hframe.grid(column = 0, row = 2, sticky = "news")
+        self.hframe.textdisplay = ttk.Frame(self.hframe)
+        self.hframe.textdisplay.pack(side = 'left', fill = 'both', expand = 'true')
+        self.hframe.textdisplay.page = tkinter.Text(self.hframe.textdisplay, wrap = 'word')
+        self.hframe.textdisplay.page.pack(side = 'top', fill = 'both', expand = 'true')
+        # Add scrollbar to text window
+        self.hframe.scrollbar = ttk.Scrollbar(self.hframe)
+        self.hframe.scrollbar.pack(side='right', fill='y')
+        # attach text window to scrollbar
+        self.hframe.textdisplay.page.config(yscrollcommand = self.hframe.scrollbar.set)
+        self.hframe.scrollbar.config(command = self.hframe.textdisplay.page.yview)
+
+        self.bbar = ttk.Frame(self)
+        self.bbar.grid(column = 0, row = 3, sticky = "news")
+        self.bbar.close_button = ttk.Button(self.bbar, text='Close',
+		command=self.close, style = 'normal.TButton')
+        self.bbar.close_button.grid(column = 0, row = 0, padx = 5)
+
+        self.rowconfigure(0, weight=0)
+        self.rowconfigure(1, weight=0)
+        self.rowconfigure(2, weight=1)
+        self.rowconfigure(3, weight=0)
+        self.columnconfigure(0, weight=1)
+
+        # Initialize with empty page
+        self.text = []
+        self.title = '(No file to display)'
+        self.timestamp = 0
+
+    def grid_configure(self, padx, pady):
+        pass
+
+    def display(self, filename=''):
+        # Read from file if text is empty
+        if filename != '':
+            if filename == self.title:
+                statbuf = os.stat(filename)
+                if self.text == [] or self.timestamp < statbuf.st_mtime:
+                    self.add_text_from_file(filename)
+                    self.timestamp = statbuf.st_mtime
+            else:
+                 self.add_text_from_file(filename)
+
+        # Remove and replace contents
+        self.hframe.textdisplay.page.delete('1.0', 'end')
+        self.hframe.textdisplay.page.insert('end', self.text)
+        self.textttitle.configure(text = self.title)
+        self.open()
+
+    # Fill the text report from a file.
+
+    def add_text_from_file(self, filename):
+        print('Loading text from file ' + filename)
+        with open(filename, 'r') as f:
+            self.text = f.read()
+        self.title = filename
+        self.display()
+
+    def close(self):
+        # pop down text window
+        self.withdraw()
+
+    def open(self):
+        # pop up text window
+        self.deiconify()
+        self.lift()
diff --git a/sky130/Makefile.in b/sky130/Makefile.in
index 8428d14..f0e35cc 100644
--- a/sky130/Makefile.in
+++ b/sky130/Makefile.in
@@ -1009,6 +1009,7 @@
 		cp -rp ${KLAYOUT_PATH}/sky130_tech/tech/sky130/pymacros/* ${KLAYOUT_STAGING_$*}/pymacros/ ; \
 		cp ${KLAYOUT_PATH}/sky130_tech/tech/sky130/${TECH}.lyp ${KLAYOUT_STAGING_$*}/tech/${SKY130$*}.lyp ; \
 		cp ${KLAYOUT_PATH}/sky130_tech/tech/sky130/${TECH}.lyt ${KLAYOUT_STAGING_$*}/tech/${SKY130$*}.lyt ; \
+		cp ${KLAYOUT_PATH}/sky130_tech/tech/sky130/${TECH}.map ${KLAYOUT_STAGING_$*}/tech/${SKY130$*}.map ; \
 	fi
 	# Copy original DRC deck from open_pdks (is this useful?)
 	cp klayout/sky130.lydrc ${KLAYOUT_STAGING_$*}/drc/${SKY130$*}.lydrc