files.new_contents: # Copyright 2020 Silicon Compiler Authors. All Rights Reserved.
  
  import base64
  import glob
  import math
  import multiprocessing
  import importlib
  import json
  import os
  import requests
  import shutil
  import subprocess
  import sys
  import time
  import urllib.parse
  import uuid
  
  from siliconcompiler.crypto import *
  from siliconcompiler import utils
  
  ###################################
  def get_base_url(chip):
      '''Helper method to get the root URL for API calls, given a Chip object.
      '''
  
      rcfg = chip.status['remote_cfg']
      remote_host = rcfg['address']
      if 'port' in rcfg:
          remote_port = rcfg['port']
      else:
          remote_port = 443
      remote_host += ':' + str(remote_port)
      if remote_host.startswith('http'):
          remote_protocol = ''
      else:
          remote_protocol = 'https://' if str(remote_port) == '443' else 'http://'
      return remote_protocol + remote_host
  
  ###################################
  def remote_preprocess(chip):
      '''Helper method to run a local import stage for remote jobs.
      '''
  
      # Assign a new 'job_hash' to the chip if necessary.
      if not 'jobhash' in chip.status:
          job_hash = uuid.uuid4().hex
          chip.status['jobhash'] = job_hash
  
      # Setup up tools for all local functions
      flow = chip.get('option', 'flow')
      remote_steplist = chip.getkeys('flowgraph', flow)
      local_step = remote_steplist[0]
      indexlist = chip.getkeys('flowgraph', flow, local_step)
      for index in indexlist:
          tool = chip.get('flowgraph', flow, local_step, index, 'tool')
          # Setting up tool is optional (step may be a builtin function)
          if tool and tool not in chip.builtin:
              chip._setup_tool(tool, local_step, index)
  
          # Need to override steplist here to make sure check_manifest() doesn't
          # check steps that haven't been setup.
          chip.set('option', 'steplist', local_step)
  
          # Run the actual import step locally.
          # We can pass in an empty 'status' dictionary, since _runtask() will
          # only look up a step's depedencies in this dictionary, and the first
          # step should have none.
          chip._runtask(local_step, index, {})
  
      # Set 'steplist' to only the remote steps, for the future server-side run.
      remote_steplist = remote_steplist[1:]
      chip.set('option', 'steplist', remote_steplist, clobber=True)
  
  ###################################
  def remote_run(chip):
      '''Helper method to run a job stage on a remote compute cluster.
      Note that files will not be copied to the remote stage; typically
      the source files will be copied into the cluster's storage before
      calling this method.
      If the "-remote" parameter was not passed in, this method
      will print a warning and do nothing.
      This method assumes that the given stage should not be skipped,
      because it is called from within the `Chip.run(...)` method.
  
      '''
  
      # Time how long the process has been running for.
      step_start = time.monotonic()
  
      # Ask the remote server to start processing the requested step.
      request_remote_run(chip)
  
      # Remove the local 'import.tar.gz' archive.
      local_archive = os.path.join(chip.get('option', 'builddir'),
                                   chip.get('design'),
                                   chip.get('option', 'jobname'),
                                   'import.tar.gz')
      if os.path.isfile(local_archive):
          os.remove(local_archive)
  
      # Check the job's progress periodically until it finishes.
      is_busy = True
      while is_busy:
        chip.logger.info("Job is still running. (%d seconds)"%(
                         int(time.monotonic() - step_start)))
        time.sleep(30)
        try:
            is_busy = is_job_busy(chip)
        except:
            # Sometimes an exception is raised if the request library cannot
            # reach the server due to a transient network issue.
            # Retrying ensures that jobs don't break off when the connection drops.
            is_busy = True
            chip.logger.info("Unknown network error encountered: retrying.")
      chip.logger.info("Remote job run completed!")
  
  ###################################
  def request_remote_run(chip):
      '''Helper method to make a web request to start a job stage.
      '''
  
      # Set the request URL.
      remote_run_url = urllib.parse.urljoin(get_base_url(chip), '/remote_run/')
  
      # Use authentication if necessary.
      job_nameid = f"{chip.get('option', 'jobname')}"
      post_params = {
          'chip_cfg': chip.cfg,
          'params': {
              'job_hash': chip.status['jobhash'],
          }
      }
      local_build_dir = os.path.join(chip.get('option', 'builddir'),
                                     chip.get('design'),
                                     job_nameid)
      rcfg = chip.status['remote_cfg']
      if ('username' in rcfg) and ('password' in rcfg):
          post_params['params']['username'] = rcfg['username']
          post_params['params']['key'] = rcfg['password']
  
      # If '-remote_user' and '-remote_key' are not both specified,
      # no authorizaion is configured; proceed without crypto.
      # If they were specified, these files are now encrypted.
      subprocess.run(['tar',
                      '-czf',
                      'import.tar.gz',
                      '--exclude',
                      'import.tar.gz',
                      '.'],
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT,
                     cwd=local_build_dir)
      upload_file = os.path.abspath(os.path.join(local_build_dir, 'import.tar.gz'))
  
      # Make the actual request, streaming the bulk data as a multipart file.
      # Redirected POST requests are translated to GETs. This is actually
      # part of the HTTP spec, so we need to manually follow the trail.
      redirect_url = remote_run_url
      while redirect_url:
          with open(upload_file, 'rb') as f:
              resp = requests.post(redirect_url,
                                   files={'import': f,
                                          'params': json.dumps(post_params)},
                                   allow_redirects=False)
              if resp.status_code == 302:
                  redirect_url = resp.headers['Location']
              elif resp.status_code >= 400:
                  chip.logger.error(resp.json()['message'])
                  chip.logger.error('Error starting remote job run; quitting.')
                  raise RuntimeError('Remote server returned unrecoverable error code.')
              else:
                  chip.logger.info(resp.text)
                  return
  
  ###################################
  def is_job_busy(chip):
      '''Helper method to make an async request asking the remote server
      whether a job is busy, or ready to accept a new step.
      Returns True if the job is busy, False if not.
      '''
  
      # Set the request URL.
      remote_run_url = urllib.parse.urljoin(get_base_url(chip), '/check_progress/')
  
      # Set common parameters.
      post_params = {
          'job_hash': chip.status['jobhash'],
          'job_id': chip.get('option', 'jobname'),
      }
  
      # Set authentication parameters if necessary.
      rcfg = chip.status['remote_cfg']
      if ('username' in rcfg) and ('password' in rcfg):
          post_params['username'] = rcfg['username']
          post_params['key'] = rcfg['password']
  
      # Make the request and print its response.
      redirect_url = remote_run_url
      while redirect_url:
          resp = requests.post(redirect_url,
                               data=json.dumps(post_params),
                               allow_redirects=False)
          if resp.status_code == 302:
              redirect_url = resp.headers['Location']
          else:
              return (resp.text != "Job has no running steps.")
  
  ###################################
  def delete_job(chip):
      '''Helper method to delete a job from shared remote storage.
      '''
  
      # Set the request URL.
      remote_run_url = urllib.parse.urljoin(get_base_url(chip), '/delete_job/')
  
      # Set common parameter.
      post_params = {
          'job_hash': chip.status['jobhash'],
      }
  
      # Set authentication parameters if necessary.
      rcfg = chip.status['remote_cfg']
      if ('username' in rcfg) and ('password' in rcfg):
          post_params['username'] = rcfg['username']
          post_params['key'] = rcfg['password']
  
      # Make the request.
      redirect_url = remote_run_url
      while redirect_url:
          resp = requests.post(redirect_url,
                               data=json.dumps(post_params),
                               allow_redirects=False)
          if resp.status_code == 302:
                  redirect_url = resp.headers['Location']
          else:
              response = resp.text
              return response
  
  ###################################
  def fetch_results_request(chip):
      '''Helper method to fetch job results from a remote compute cluster.
      '''
  
      # Set the request URL.
      job_hash = chip.status['jobhash']
      remote_run_url = urllib.parse.urljoin(get_base_url(chip), '/get_results/' + job_hash + '.tar.gz')
  
      # Set authentication parameters if necessary.
      rcfg = chip.status['remote_cfg']
      if ('username' in rcfg) and ('password' in rcfg):
          post_params = {
              'username': rcfg['username'],
              'key': rcfg['password'],
          }
      else:
          post_params = {}
  
      # Make the web request, and stream the results archive in chunks.
      redirect_url = remote_run_url
      can_redirect = False
      while redirect_url:
          with open('%s.tar.gz'%job_hash, 'wb') as zipf:
              resp = requests.post(redirect_url,
                                   data=json.dumps(post_params),
                                   allow_redirects=can_redirect,
                                   stream=True)
              if resp.status_code == 302:
                  redirect_url = resp.headers['Location']
              elif resp.status_code == 303:
                  redirect_url = resp.headers['Location']
                  can_redirect = True
              else:
                  shutil.copyfileobj(resp.raw, zipf)
                  return
  
  ###################################
  def fetch_results(chip):
      '''Helper method to fetch and open job results from a remote compute cluster.
      '''
  
      # Fetch the remote archive after the export stage.
      fetch_results_request(chip)
  
      # Call 'delete_job' to remove the run from the server.
      delete_job(chip)
  
      # Unzip the results.
      top_design = chip.get('design')
      job_hash = chip.status['jobhash']
      local_dir = chip.get('option', 'builddir')
  
      # Authenticated jobs get a zip file full of other zip files.
      # So we need to extract and delete those.
      subprocess.run(['tar', '-xzf', f'{job_hash}.tar.gz'])
      # Remove the results archive after it is extracted.
      os.remove(f'{job_hash}.tar.gz')
  
      # Remove dangling 'import' symlinks if necessary.
      for import_link in glob.iglob(job_hash + '/' + top_design + '/**/import*',
                                    recursive=True):
          if os.path.islink(import_link):
              os.remove(import_link)
      # Copy the results into the local build directory, and remove the
      # unzipped directory (including encrypted archives).
      utils.copytree(job_hash,
                     local_dir,
                     dirs_exist_ok = True)
      shutil.rmtree(job_hash)
  
  
  [... the rest is omitted ...]
files.new_path: siliconcompiler/client.py
files.old_contents: # Copyright 2020 Silicon Compiler Authors. All Rights Reserved.
  
  import base64
  import glob
  import math
  import multiprocessing
  import importlib
  import json
  import os
  import requests
  import shutil
  import subprocess
  import sys
  import time
  import urllib.parse
  import uuid
  
  from siliconcompiler.crypto import *
  from siliconcompiler import utils
  
  ###################################
  def get_base_url(chip):
      '''Helper method to get the root URL for API calls, given a Chip object.
      '''
  
      rcfg = chip.status['remote_cfg']
      remote_host = rcfg['address']
      if 'port' in rcfg:
          remote_port = rcfg['port']
      else:
          remote_port = 443
      remote_host += ':' + str(remote_port)
      if remote_host.startswith('http'):
          remote_protocol = ''
      else:
          remote_protocol = 'https://' if str(remote_port) == '443' else 'http://'
      return remote_protocol + remote_host
  
  ###################################
  def remote_preprocess(chip):
      '''Helper method to run a local import stage for remote jobs.
      '''
  
      # Assign a new 'job_hash' to the chip if necessary.
      if not 'jobhash' in chip.status:
          job_hash = uuid.uuid4().hex
          chip.status['jobhash'] = job_hash
  
      # Setup up tools for all local functions
      flow = chip.get('option', 'flow')
      remote_steplist = chip.getkeys('flowgraph', flow)
      local_step = remote_steplist[0]
      indexlist = chip.getkeys('flowgraph', flow, local_step)
      for index in indexlist:
          tool = chip.get('flowgraph', flow, local_step, index, 'tool')
          # Setting up tool is optional (step may be a builtin function)
          if tool:
              chip.set('arg', 'step', local_step)
              chip.set('arg', 'index', index)
              func = chip.find_function(tool, 'setup', 'tools')
              func(chip)
  
          # Need to override steplist here to make sure check_manifest() doesn't
          # check steps that haven't been setup.
          chip.set('option', 'steplist', local_step)
  
          # Run the actual import step locally.
          # We can pass in an empty 'status' dictionary, since _runtask() will
          # only look up a step's depedencies in this dictionary, and the first
          # step should have none.
          chip._runtask(local_step, index, {})
  
      # Set 'steplist' to only the remote steps, for the future server-side run.
      remote_steplist = remote_steplist[1:]
      chip.set('option', 'steplist', remote_steplist, clobber=True)
  
  ###################################
  def remote_run(chip):
      '''Helper method to run a job stage on a remote compute cluster.
      Note that files will not be copied to the remote stage; typically
      the source files will be copied into the cluster's storage before
      calling this method.
      If the "-remote" parameter was not passed in, this method
      will print a warning and do nothing.
      This method assumes that the given stage should not be skipped,
      because it is called from within the `Chip.run(...)` method.
  
      '''
  
      # Time how long the process has been running for.
      step_start = time.monotonic()
  
      # Ask the remote server to start processing the requested step.
      request_remote_run(chip)
  
      # Remove the local 'import.tar.gz' archive.
      local_archive = os.path.join(chip.get('option', 'builddir'),
                                   chip.get('design'),
                                   chip.get('option', 'jobname'),
                                   'import.tar.gz')
      if os.path.isfile(local_archive):
          os.remove(local_archive)
  
      # Check the job's progress periodically until it finishes.
      is_busy = True
      while is_busy:
        chip.logger.info("Job is still running. (%d seconds)"%(
                         int(time.monotonic() - step_start)))
        time.sleep(30)
        try:
            is_busy = is_job_busy(chip)
        except:
            # Sometimes an exception is raised if the request library cannot
            # reach the server due to a transient network issue.
            # Retrying ensures that jobs don't break off when the connection drops.
            is_busy = True
            chip.logger.info("Unknown network error encountered: retrying.")
      chip.logger.info("Remote job run completed!")
  
  ###################################
  def request_remote_run(chip):
      '''Helper method to make a web request to start a job stage.
      '''
  
      # Set the request URL.
      remote_run_url = urllib.parse.urljoin(get_base_url(chip), '/remote_run/')
  
      # Use authentication if necessary.
      job_nameid = f"{chip.get('option', 'jobname')}"
      post_params = {
          'chip_cfg': chip.cfg,
          'params': {
              'job_hash': chip.status['jobhash'],
          }
      }
      local_build_dir = os.path.join(chip.get('option', 'builddir'),
                                     chip.get('design'),
                                     job_nameid)
      rcfg = chip.status['remote_cfg']
      if ('username' in rcfg) and ('password' in rcfg):
          post_params['params']['username'] = rcfg['username']
          post_params['params']['key'] = rcfg['password']
  
      # If '-remote_user' and '-remote_key' are not both specified,
      # no authorizaion is configured; proceed without crypto.
      # If they were specified, these files are now encrypted.
      subprocess.run(['tar',
                      '-czf',
                      'import.tar.gz',
                      '--exclude',
                      'import.tar.gz',
                      '.'],
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT,
                     cwd=local_build_dir)
      upload_file = os.path.abspath(os.path.join(local_build_dir, 'import.tar.gz'))
  
      # Make the actual request, streaming the bulk data as a multipart file.
      # Redirected POST requests are translated to GETs. This is actually
      # part of the HTTP spec, so we need to manually follow the trail.
      redirect_url = remote_run_url
      while redirect_url:
          with open(upload_file, 'rb') as f:
              resp = requests.post(redirect_url,
                                   files={'import': f,
                                          'params': json.dumps(post_params)},
                                   allow_redirects=False)
              if resp.status_code == 302:
                  redirect_url = resp.headers['Location']
              elif resp.status_code >= 400:
                  chip.logger.error(resp.json()['message'])
                  chip.logger.error('Error starting remote job run; quitting.')
                  raise RuntimeError('Remote server returned unrecoverable error code.')
              else:
                  chip.logger.info(resp.text)
                  return
  
  ###################################
  def is_job_busy(chip):
      '''Helper method to make an async request asking the remote server
      whether a job is busy, or ready to accept a new step.
      Returns True if the job is busy, False if not.
      '''
  
      # Set the request URL.
      remote_run_url = urllib.parse.urljoin(get_base_url(chip), '/check_progress/')
  
      # Set common parameters.
      post_params = {
          'job_hash': chip.status['jobhash'],
          'job_id': chip.get('option', 'jobname'),
      }
  
      # Set authentication parameters if necessary.
      rcfg = chip.status['remote_cfg']
      if ('username' in rcfg) and ('password' in rcfg):
          post_params['username'] = rcfg['username']
          post_params['key'] = rcfg['password']
  
      # Make the request and print its response.
      redirect_url = remote_run_url
      while redirect_url:
          resp = requests.post(redirect_url,
                               data=json.dumps(post_params),
                               allow_redirects=False)
          if resp.status_code == 302:
              redirect_url = resp.headers['Location']
          else:
              return (resp.text != "Job has no running steps.")
  
  ###################################
  def delete_job(chip):
      '''Helper method to delete a job from shared remote storage.
      '''
  
      # Set the request URL.
      remote_run_url = urllib.parse.urljoin(get_base_url(chip), '/delete_job/')
  
      # Set common parameter.
      post_params = {
          'job_hash': chip.status['jobhash'],
      }
  
      # Set authentication parameters if necessary.
      rcfg = chip.status['remote_cfg']
      if ('username' in rcfg) and ('password' in rcfg):
          post_params['username'] = rcfg['username']
          post_params['key'] = rcfg['password']
  
      # Make the request.
      redirect_url = remote_run_url
      while redirect_url:
          resp = requests.post(redirect_url,
                               data=json.dumps(post_params),
                               allow_redirects=False)
          if resp.status_code == 302:
                  redirect_url = resp.headers['Location']
          else:
              response = resp.text
              return response
  
  ###################################
  def fetch_results_request(chip):
      '''Helper method to fetch job results from a remote compute cluster.
      '''
  
      # Set the request URL.
      job_hash = chip.status['jobhash']
      remote_run_url = urllib.parse.urljoin(get_base_url(chip), '/get_results/' + job_hash + '.tar.gz')
  
      # Set authentication parameters if necessary.
      rcfg = chip.status['remote_cfg']
      if ('username' in rcfg) and ('password' in rcfg):
          post_params = {
              'username': rcfg['username'],
              'key': rcfg['password'],
          }
      else:
          post_params = {}
  
      # Make the web request, and stream the results archive in chunks.
      redirect_url = remote_run_url
      can_redirect = False
      while redirect_url:
          with open('%s.tar.gz'%job_hash, 'wb') as zipf:
              resp = requests.post(redirect_url,
                                   data=json.dumps(post_params),
                                   allow_redirects=can_redirect,
                                   stream=True)
              if resp.status_code == 302:
                  redirect_url = resp.headers['Location']
              elif resp.status_code == 303:
                  redirect_url = resp.headers['Location']
                  can_redirect = True
              else:
                  shutil.copyfileobj(resp.raw, zipf)
                  return
  
  ###################################
  def fetch_results(chip):
      '''Helper method to fetch and open job results from a remote compute cluster.
      '''
  
      # Fetch the remote archive after the export stage.
      fetch_results_request(chip)
  
      # Call 'delete_job' to remove the run from the server.
      delete_job(chip)
  
      # Unzip the results.
      top_design = chip.get('design')
      job_hash = chip.status['jobhash']
      local_dir = chip.get('option', 'builddir')
  
      # Authenticated jobs get a zip file full of other zip files.
      # So we need to extract and delete those.
      subprocess.run(['tar', '-xzf', f'{job_hash}.tar.gz'])
      # Remove the results archive after it is extracted.
      os.remove(f'{job_hash}.tar.gz')
  
      # Remove dangling 'import' symlinks if necessary.
      for import_link in glob.iglob(job_hash + '/' + top_design + '/**/import*',
                                    recursive=True):
          if os.path.islink(import_link):
              os.remove(import_link)
      # Copy the results into the local build directory, and remove the
      # unzipped directory (including encrypted archives).
      utils.copytree(job_hash,
                     local_dir,
                     dirs_exist_ok = True)
      shutil.rmtree(job_hash)
  
  
  [... the rest is omitted ...]
files.old_path: siliconcompiler/client.py
files.new_contents: # Copyright 2020 Silicon Compiler Authors. All Rights Reserved.
  
  import argparse
  import base64
  import time
  import datetime
  import multiprocessing
  import tarfile
  import traceback
  import asyncio
  from subprocess import run, PIPE
  import os
  import glob
  import pathlib
  import sys
  import gzip
  import re
  import json
  import logging
  import hashlib
  import shutil
  import copy
  import importlib
  import textwrap
  import math
  import pandas
  import yaml
  import graphviz
  import time
  import uuid
  import shlex
  import platform
  import getpass
  import csv
  import distro
  import netifaces
  import webbrowser
  import packaging.version
  import packaging.specifiers
  from jinja2 import Environment, FileSystemLoader
  from pathlib import Path
  from timeit import default_timer as timer
  from siliconcompiler.client import *
  from siliconcompiler.schema import *
  from siliconcompiler.scheduler import _deferstep
  from siliconcompiler import leflib
  from siliconcompiler import utils
  from siliconcompiler import _metadata
  import psutil
  
  class TaskStatus():
      # Could use Python 'enum' class here, but that doesn't work nicely with
      # schema.
      PENDING = 'pending'
      SUCCESS = 'success'
      ERROR = 'error'
  
  class Chip:
      """Object for configuring and executing hardware design flows.
  
      This is the main object used for configuration, data, and
      execution within the SiliconCompiler platform.
  
      Args:
          design (string): Name of the top level chip design module.
  
      Examples:
          >>> siliconcompiler.Chip(design="top")
          Creates a chip object with name "top".
      """
  
      ###########################################################################
      def __init__(self, design, loglevel=None):
          # version numbers
          self.scversion = _metadata.version
          self.schemaversion = SCHEMA_VERSION
  
          # Local variables
          self.scroot = os.path.dirname(os.path.abspath(__file__))
          self.cwd = os.getcwd()
          self.error = 0
          self.cfg = schema_cfg()
          # The 'status' dictionary can be used to store ephemeral config values.
          # Its contents will not be saved, and can be set by parent scripts
          # such as a web server or supervisor process. Currently supported keys:
          # * 'jobhash': A hash or UUID which can identify jobs in a larger system.
          # * 'remote_cfg': Dictionary containing remote server configurations
          #                 (address, credentials, etc.)
          # * 'slurm_account': User account ID in a connected slurm HPC cluster.
          # * 'slurm_partition': Name of the partition in which a task should run
          #                      on a connected slurm HPC cluster.
          # * 'watchdog': Activity-monitoring semaphore for jobs scheduled on an
          #               HPC cluster; expects a 'threading.Event'-like object.
          # * 'max_fs_bytes': A limit on how much disk space a job is allowed
          #                   to consume in a connected HPC cluster's storage.
          self.status = {}
  
          self.builtin = ['minimum','maximum',
                          'nop', 'mux', 'join', 'verify']
  
          # We set 'design' and 'loglevel' directly in the config dictionary
          # because of a chicken-and-egg problem: self.set() relies on the logger,
          # but the logger relies on these values.
          self.cfg['design']['value'] = design
          if loglevel:
              self.cfg['option']['loglevel']['value'] = loglevel
  
          self._init_logger()
  
          self._loaded_modules = {
              'flows': [],
              'pdks': [],
              'libs': [],
              'checklists': []
          }
  
      ###########################################################################
      @property
      def design(self):
          '''Design name of chip object.
  
          This is an immutable property.'''
          return self.get('design')
  
      ###########################################################################
      def _init_logger(self, step=None, index=None, in_run=False):
  
          self.logger = logging.getLogger(uuid.uuid4().hex)
  
          # Don't propagate log messages to "root" handler (we get duplicate
          # messages without this)
          # TODO: this prevents us from being able to capture logs with pytest:
          # we should revisit it
          self.logger.propagate = False
  
          loglevel = self.get('option', 'loglevel')
  
          if loglevel=='DEBUG':
              prefix = '| %(levelname)-7s | %(funcName)-10s | %(lineno)-4s'
          else:
              prefix = '| %(levelname)-7s'
  
          if in_run:
              flow = self.get('option', 'flow')
  
              # Figure out how wide to make step and index fields
              max_step_len = 2
              max_index_len = 2
              for future_step in self.getkeys('flowgraph', flow):
                  max_step_len = max(len(future_step) + 1, max_step_len)
                  for future_index in self.getkeys('flowgraph', flow, future_step):
                      max_index_len = max(len(future_index) + 1, max_index_len)
  
              jobname = self.get('option', 'jobname')
  
              if step is None:
                  step = '-' * max(max_step_len // 4, 1)
              if index is None:
                  index = '-' * max(max_index_len // 4, 1)
  
              run_info = f'%s  | %-{max_step_len}s | %-{max_index_len}s' % (jobname, step, index)
              logformat = ' | '.join([prefix, run_info, '%(message)s'])
          else:
              logformat = ' | '.join([prefix, '%(message)s'])
  
          handler = logging.StreamHandler()
          formatter = logging.Formatter(logformat)
  
          handler.setFormatter(formatter)
  
          # Clear any existing handlers so we don't end up with duplicate messages
          # if repeat calls to _init_logger are made
          if len(self.logger.handlers) > 0:
              self.logger.handlers.clear()
  
          self.logger.addHandler(handler)
          self.logger.setLevel(loglevel)
  
      ###########################################################################
      def _deinit_logger(self):
          self.logger = None
  
      ###########################################################################
      def _get_switches(self, *keypath):
          '''Helper function for parsing switches and metavars for a keypath.'''
          #Switch field fully describes switch format
          switch = self.get(*keypath, field='switch')
  
          if switch is None:
              switches = []
          elif isinstance(switch, list):
              switches = switch
          else:
              switches = [switch]
          switchstrs = []
  
          # parse out switch from metavar
          # TODO: should we validate that metavar matches for each switch?
          for switch in switches:
              switchmatch = re.match(r'(-[\w_]+)\s+(.*)', switch)
              gccmatch = re.match(r'(-[\w_]+)(.*)', switch)
              plusmatch = re.match(r'(\+[\w_\+]+)(.*)', switch)
  
              if switchmatch:
                  switchstr = switchmatch.group(1)
                  metavar = switchmatch.group(2)
              elif gccmatch:
                  switchstr = gccmatch.group(1)
                  metavar = gccmatch.group(2)
              elif plusmatch:
                  switchstr = plusmatch.group(1)
                  metavar = plusmatch.group(2)
              switchstrs.append(switchstr)
  
          return switchstrs, metavar
  
      ###########################################################################
      def create_cmdline(self, progname, description=None, switchlist=None, input_map=None):
          """Creates an SC command line interface.
  
          Exposes parameters in the SC schema as command line switches,
          simplifying creation of SC apps with a restricted set of schema
          parameters exposed at the command line. The order of command
          line switch settings parsed from the command line is as follows:
  
           1. loglevel
           2. fpga_partname
           3. load_target('target')
           4. read_manifest([cfg])
           5. all other switches
  
          The cmdline interface is implemented using the Python argparse package
          and the following use restrictions apply.
  
          * Help is accessed with the '-h' switch.
          * Arguments that include spaces must be enclosed with double quotes.
          * List parameters are entered individually. (ie. -y libdir1 -y libdir2)
          * For parameters with Boolean types, the switch implies "true".
          * Special characters (such as '-') must be enclosed in double quotes.
          * Compiler compatible switches include: -D, -I, -O{0,1,2,3}
          * Verilog legacy switch formats are supported: +libext+, +incdir+
  
          Args:
              progname (str): Name of program to be executed.
              description (str): Short program description.
              switchlist (list of str): List of SC parameter switches to expose
                  at the command line. By default all SC schema switches are
                  available. Parameter switches should be entered based on the
                  parameter 'switch' field in the schema. For parameters with
                  multiple switches, both will be accepted if any one is included
                  in this list.
              input_map (dict of str): Dictionary mapping file extensions to input
                  filetypes. This is used to automatically assign positional
                  source arguments to ['input', ...] keypaths based on their file
                  extension. If None, the CLI will not accept positional source
                  arguments.
  
          Examples:
              >>> chip.create_cmdline(progname='sc-show',switchlist=['-input','-cfg'])
              Creates a command line interface for 'sc-show' app.
  
              >>> chip.create_cmdline(progname='sc', input_map={'v': 'verilog'})
              All sources ending in .v will be stored in ['input', 'verilog']
          """
  
          # Argparse
          parser = argparse.ArgumentParser(prog=progname,
                                           prefix_chars='-+',
                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                           description=description)
  
          # Get all keys from global dictionary or override at command line
          allkeys = self.getkeys()
  
          # Iterate over all keys to add parser arguments
          for keypath in allkeys:
              #Fetch fields from leaf cell
              helpstr = self.get(*keypath, field='shorthelp')
              typestr = self.get(*keypath, field='type')
  
              # argparse 'dest' must be a string, so join keypath with commas
              dest = '_'.join(keypath)
  
              switchstrs, metavar = self._get_switches(*keypath)
  
              # Three switch types (bool, list, scalar)
              if not switchlist or any(switch in switchlist for switch in switchstrs):
                  if typestr == 'bool':
                      parser.add_argument(*switchstrs,
                                          nargs='?',
                                          metavar=metavar,
                                          dest=dest,
                                          const='true',
                                          help=helpstr,
                                          default=argparse.SUPPRESS)
                  #list type arguments
                  elif re.match(r'\[', typestr):
                      #all the rest
                      parser.add_argument(*switchstrs,
                                          metavar=metavar,
                                          dest=dest,
                                          action='append',
                                          help=helpstr,
                                          default=argparse.SUPPRESS)
                  else:
                      #all the rest
                      parser.add_argument(*switchstrs,
                                          metavar=metavar,
                                          dest=dest,
                                          help=helpstr,
                                          default=argparse.SUPPRESS)
  
          if input_map is not None:
              parser.add_argument('source',
                                  nargs='*',
                                  help='Input files with filetype inferred by extension')
  
          #Preprocess sys.argv to enable linux commandline switch formats
          #(gcc, verilator, etc)
          scargs = []
  
          # Iterate from index 1, otherwise we end up with script name as a
          # 'source' positional argument
          for item in sys.argv[1:]:
              #Split switches with one character and a number after (O0,O1,O2)
              opt = re.match(r'(\-\w)(\d+)', item)
              #Split assign switches (-DCFG_ASIC=1)
              assign = re.search(r'(\-\w)(\w+\=\w+)', item)
              #Split plusargs (+incdir+/path)
              plusarg = re.search(r'(\+\w+\+)(.*)', item)
              if opt:
                  scargs.append(opt.group(1))
                  scargs.append(opt.group(2))
              elif plusarg:
                  scargs.append(plusarg.group(1))
                  scargs.append(plusarg.group(2))
              elif assign:
                  scargs.append(assign.group(1))
                  scargs.append(assign.group(2))
              else:
                  scargs.append(item)
  
          parser.add_argument('-version', action='version', version=_metadata.version)
  
          #Grab argument from pre-process sysargs
          cmdargs = vars(parser.parse_args(scargs))
  
          # Print banner
          print(_metadata.banner)
          print("Authors:", ", ".join(_metadata.authors))
          print("Version:", _metadata.version, "\n")
          print("-"*80)
  
          os.environ["COLUMNS"] = '80'
  
          # 1. set loglevel if set at command line
          if 'option_loglevel' in cmdargs.keys():
              self.logger.setLevel(cmdargs['option_loglevel'])
  
          # 2. read in target if set
          if 'option_target' in cmdargs.keys():
              if 'arg_pdk' in cmdargs.keys():
                  raise NotImplementedError("NOT IMPLEMENTED: ['arg', 'pdk'] parameter with target")
              if 'arg_flow' in cmdargs.keys():
                  raise NotImplementedError("NOT IMPLEMENTED: ['arg', 'flow'] parameter with target")
              if 'fpga_partname' in cmdargs.keys():
                  self.set('fpga', 'partname', cmdargs['fpga_partname'], clobber=True)
              # running target command
              self.load_target(cmdargs['option_target'])
  
          # 4. read in all cfg files
          if 'option_cfg' in cmdargs.keys():
              for item in cmdargs['option_cfg']:
                  self.read_manifest(item, clobber=True, clear=True)
  
          # Map sources to ['input'] keypath.
          if 'source' in cmdargs:
              for source in cmdargs['source']:
                  _, ext = os.path.splitext(source)
                  ext = ext.lstrip('.')
                  if ext in input_map:
                      filetype = input_map[ext]
                      if self.valid('input', filetype, quiet=True):
                          self.add('input', filetype, source)
                      else:
                          self.set('input', filetype, source)
                      self.logger.info(f'Source {source} inferred as {filetype}')
                  else:
                      self.logger.warning('Unable to infer input type for '
                          f'{source} based on file extension, ignoring. Use the '
                          '-input flag to provide it explicitly.')
              # we don't want to handle this in the next loop
              del cmdargs['source']
  
          # 5. Cycle through all command args and write to manifest
          for dest, vals in cmdargs.items():
              keypath = dest.split('_')
  
              # Turn everything into a list for uniformity
              if not isinstance(vals, list):
                  vals = [vals]
  
              # Cycle through all items
              for item in vals:
                  # Hack to handle the fact that we want optmode stored with an 'O'
                  # prefix.
                  if keypath == ['option', 'optmode']:
                      item = 'O' + item
  
                  num_free_keys = keypath.count('default')
  
                  if len(item.split(' ')) < num_free_keys + 1:
                      # Error out if value provided doesn't have enough words to
                      # fill in 'default' keys.
                      switches, metavar = self._get_switches(*keypath)
                      switchstr = '/'.join(switches)
                      self.logger.error(f'Invalid value {item} for switch {switchstr}. Expected format {metavar}.')
                      raise SiliconCompilerError('Invalid CLI arguments')
  
                  # We replace 'default' in keypath with first N words in provided
                  # value. Remainder is the actual value we want to store in the
                  # parameter.
                  *free_keys, val = item.split(' ', num_free_keys)
                  args = [free_keys.pop(0) if key == 'default' else key for key in keypath]
  
                  # Storing in manifest
                  self.logger.info(f"Command line argument entered: {args} Value: {val}")
                  typestr = self.get(*keypath, field='type')
                  if typestr.startswith('['):
                      if self.valid(*args, quiet=True):
                          self.add(*args, val)
                      else:
                          self.set(*args, val, clobber=True)
                  else:
                      self.set(*args, val, clobber=True)
  
      #########################################################################
      def find_function(self, modulename, funcname, moduletype=None):
          '''
          Returns a function attribute from a module on disk.
  
          Searches the SC root directory and the 'scpath' parameter for the
          modulename provided and imports the module if found. If the funcname
          provided is found in the module, a callable function attribute is
          returned, otherwise None is returned.
  
          The function assumes the following directory structure:
  
          * tools/modulename/modulename.py
          * flows/modulename.py
          * pdks/modulname.py
  
          If the moduletype is None, the module paths are search in the
          order: 'targets'->'flows'->'tools'->'pdks'->'libs'->'checklists'):
  
  
          Supported functions include:
  
          * targets (make_docs, setup)
          * pdks (make_docs, setup)
          * flows (make_docs, setup)
          * tools (make_docs, setup, check_version, runtime_options,
            pre_process, post_process)
          * libs (make_docs, setup)
  
          Args:
              modulename (str): Name of module to import.
              funcname (str): Name of the function to find within the module.
              moduletype (str): Type of module (flows, pdks, libs, checklists, targets).
  
          Examples:
              >>> setup_pdk = chip.find_function('freepdk45', 'setup', 'pdks')
              >>> setup_pdk()
              Imports the freepdk45 module and runs the setup_pdk function
  
          '''
  
          # module search path depends on modtype
          if moduletype is None:
              for item in ('targets', 'flows', 'tools', 'pdks', 'libs', 'checklists'):
                  fullpath = self.find_function(modulename, funcname, module_type=item)
                  if fullpath:
                      break
                  self.logger.error(f"Could not find module {modulename}")
                  self.error = 1
                  return None
          elif moduletype in ('targets','flows', 'pdks', 'libs'):
              fullpath = self._find_sc_file(f"{moduletype}/{modulename}.py", missing_ok=True)
          elif moduletype in ('tools', 'checklists'):
              fullpath = self._find_sc_file(f"{moduletype}/{modulename}/{modulename}.py", missing_ok=True)
          else:
              self.logger.error(f"Illegal module type '{moduletype}'.")
              self.error = 1
              return
  
          # try loading module if found
          if fullpath:
              self.logger.debug(f"Loading function '{funcname}' from module '{modulename}'")
  
              try:
                  spec = importlib.util.spec_from_file_location(modulename, fullpath)
                  imported = importlib.util.module_from_spec(spec)
                  spec.loader.exec_module(imported)
  
                  if hasattr(imported, funcname):
                      function = getattr(imported, funcname)
                  else:
                      function = None
                  return function
              except:
                  traceback.print_exc()
                  self.logger.error(f"Module setup failed for '{modulename}'")
                  self.error = 1
  
      ##########################################################################
      def load_target(self, name):
          """
          Loads a target module and runs the setup() function.
  
          The function searches the $SCPATH for targets/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
              flow (str): Target flow to
  
          Examples:
              >>> chip.load_target('freepdk45_demo')
              Loads the 'freepdk45_demo' target
  
          """
  
          self.set('option', 'target', name)
  
          func = self.find_function(name, 'setup', 'targets')
          if func is not None:
              func(self)
          else:
              self.logger.error(f'Target module {name} not found in $SCPATH or siliconcompiler/targets/.')
              raise SiliconCompilerError(f'Target module {name} not found $SCPATH or siliconcompiler/targets/.')
  
      ##########################################################################
      def load_pdk(self, name):
          """
          Loads a PDK module and runs the setup() function.
  
          The function searches the $SCPATH for pdks/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
  
          Examples:
              >>> chip.load_pdk('freepdk45_pdk')
              Loads the 'freepdk45' pdk
  
          """
  
          func = self.find_function(name, 'setup', 'pdks')
          if func is not None:
              self.logger.info(f"Loading PDK '{name}'")
              self._loaded_modules['pdks'].append(name)
              func(self)
          else:
              self.logger.error(f'PDK module {name} not found in $SCPATH or siliconcompiler/pdks/.')
              raise SiliconCompilerError(f'PDK module {name} not found in $SCPATH or siliconcompiler/pdks/.')
  
      ##########################################################################
      def load_flow(self, name):
          """
          Loads a flow  module and runs the setup() function.
  
          The function searches the $SCPATH for flows/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
  
          Examples:
              >>> chip.load_flow('asicflow')
              Loads the 'asicflow' flow
  
          """
  
          func = self.find_function(name, 'setup', 'flows')
          if func is not None:
              self.logger.info(f"Loading flow '{name}'")
              self._loaded_modules['flows'].append(name)
              func(self)
          else:
              self.logger.error(f'Flow module {name} not found in $SCPATH or siliconcompiler/flows/.')
              raise SiliconCompilerError(f'Flow module {name} not found in $SCPATH or siliconcompiler/flows/.')
  
      ##########################################################################
      def load_lib(self, name):
          """
          Loads a library module and runs the setup() function.
  
          The function searches the $SCPATH for libs/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
  
          Examples:
              >>> chip.load_lib('nangate45')
              Loads the 'nangate45' library
  
          """
  
          func = self.find_function(name, 'setup', 'libs')
          if func is not None:
              self.logger.info(f"Loading library '{name}'")
              self._loaded_modules['libs'].append(name)
              func(self)
          else:
              self.logger.error(f'Library module {name} not found in $SCPATH or siliconcompiler/libs/.')
              raise SiliconCompilerError(f'Library module {name} not found in $SCPATH or siliconcompiler/libs/.')
  
      ##########################################################################
      def load_checklist(self, name):
          """
          Loads a checklist module and runs the setup() function.
  
          The function searches the $SCPATH for checklist/<name>/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
  
          Examples:
              >>> chip.load_checklist('oh_tapeout')
              Loads the 'oh_tapeout' checklist
  
          """
  
          func = self.find_function(name, 'setup', 'checklists')
          if func is not None:
              self.logger.info(f"Loading checklist '{name}'")
              self._loaded_modules['checklists'].append(name)
              func(self)
          else:
              self.logger.error(f'Checklist module {name} not found in $SCPATH or siliconcompiler/checklists/.')
              raise SiliconCompilerError(f'Checklist module {name} not found in $SCPATH or siliconcompiler/checklists/.')
  
  
      ###########################################################################
      def list_metrics(self):
          '''
          Returns a list of all metrics in the schema.
  
          '''
  
          return self.getkeys('metric','default','default')
  
      ###########################################################################
      def help(self, *keypath):
          """
          Returns a schema parameter description.
  
          Args:
              *keypath(str): Keypath to parameter.
  
          Returns:
              A formatted multi-line help paragraph for the parameter provided.
  
          Examples:
              >>> print(chip.help('asic','diearea'))
              Displays help information about the 'asic, diearea' parameter
  
          """
  
          self.logger.debug('Fetching help for %s', keypath)
  
          #Fetch Values
  
          description = self.get(*keypath, field='shorthelp')
          typestr = self.get(*keypath, field='type')
          switchstr = str(self.get(*keypath, field='switch'))
          defstr = str(self.get(*keypath, field='defvalue'))
          requirement = str(self.get(*keypath, field='require'))
          helpstr = self.get(*keypath, field='help')
          example = self.get(*keypath, field='example')
  
          examplestr = ("\nExamples:    " + example[0] + ''.join(
                       ["\n             " + ex for ex in example[1:]]))
  
          #Removing multiple spaces and newlines
          helpstr = helpstr.rstrip()
          helpstr = helpstr.replace("\n", "")
          helpstr = ' '.join(helpstr.split())
  
          for idx, item in enumerate(example):
              example[idx] = ' '.join(item.split())
              example[idx] = example[idx].replace(", ", ",")
  
          #Wrap text
          para = textwrap.TextWrapper(width=60)
          para_list = para.wrap(text=helpstr)
  
          #Full Doc String
          fullstr = ("-"*80 +
                     "\nDescription: " + description +
                     "\nSwitch:      " + switchstr +
                     "\nType:        " + typestr  +
                     "\nRequirement: " + requirement   +
                     "\nDefault:     " + defstr   +
                     examplestr +
                     "\nHelp:        " + para_list[0] + "\n")
          for line in para_list[1:]:
              fullstr = (fullstr +
                         " "*13 + line.lstrip() + "\n")
  
          return fullstr
  
  
      ###########################################################################
      def valid(self, *args, valid_keypaths=None, quiet=True, default_valid=False):
          """
          Checks validity of a keypath.
  
          Checks the validity of a parameter keypath and returns True if the
          keypath is valid and False if invalid.
  
          Args:
              keypath(list str): Variable length schema key list.
              valid_keypaths (list of list): List of valid keypaths as lists. If
                  None, check against all keypaths in the schema.
              quiet (bool): If True, don't display warnings for invalid keypaths.
  
          Returns:
              Boolean indicating validity of keypath.
  
          Examples:
              >>> check = chip.valid('design')
              Returns True.
              >>> check = chip.valid('blah')
              Returns False.
          """
  
          keypathstr = ','.join(args)
          keylist = list(args)
          if default_valid:
              default = 'default'
          else:
              default = None
  
          if valid_keypaths is None:
              valid_keypaths = self.getkeys()
  
          # Look for a full match with default playing wild card
          for valid_keypath in valid_keypaths:
              if len(keylist) != len(valid_keypath):
                  continue
  
              ok = True
              for i in range(len(keylist)):
                  if valid_keypath[i] not in (keylist[i], default):
                      ok = False
                      break
              if ok:
                  return True
  
          # Match not found
          if not quiet:
              self.logger.warning(f"Keypath [{keypathstr}] is not valid")
          return False
  
      ###########################################################################
      def get(self, *keypath, field='value', job=None, cfg=None):
          """
          Returns a schema parameter field.
  
          Returns a schema parameter field based on the keypath provided in the
          ``*keypath``. See the :ref:`Schema Reference Manual<SiliconCompiler
          Schema>` for documentation of all supported keypaths. The returned type
          is consistent with the type field of the parameter. Fetching parameters
          with empty or undefined value files returns None for scalar types and []
          (empty list) for list types.  Accessing a non-existent keypath produces
          a logger error message and raises the Chip object error flag.
  
          Args:
              keypath(list str): Variable length schema key list.
              field(str): Parameter field to fetch.
              job (str): Jobname to use for dictionary access in place of the
                  current active jobname.
              cfg(dict): Alternate dictionary to access in place of the default
                  chip object schema dictionary.
  
          Returns:
              Value found for the keypath and field provided.
  
          Examples:
              >>> foundry = chip.get('pdk', 'foundry')
              Returns the name of the foundry from the PDK.
  
          """
  
          if cfg is None:
              if job is not None:
                  cfg = self.cfg['history'][job]
              else:
                  cfg = self.cfg
  
          keypathstr = ','.join(keypath)
  
          self.logger.debug(f"Reading from [{keypathstr}]. Field = '{field}'")
          return self._search(cfg, keypathstr, *keypath, field=field, mode='get')
  
      ###########################################################################
      def getkeys(self, *keypath, cfg=None, job=None):
          """
          Returns a list of schema dictionary keys.
  
          Searches the schema for the keypath provided and returns a list of
          keys found, excluding the generic 'default' key. Accessing a
          non-existent keypath produces a logger error message and raises the
          Chip object error flag.
  
          Args:
              keypath (list str): Variable length ordered schema key list
              cfg (dict): Alternate dictionary to access in place of self.cfg
              job (str): Jobname to use for dictionary access in place of the
                  current active jobname.
  
          Returns:
              List of keys found for the keypath provided.
  
          Examples:
              >>> keylist = chip.getkeys('pdk')
              Returns all keys for the 'pdk' keypath.
              >>> keylist = chip.getkeys()
              Returns all list of all keypaths in the schema.
          """
  
          if cfg is None:
              if job is None:
                  cfg = self.cfg
              else:
                  cfg = self.cfg['history'][job]
  
          if len(list(keypath)) > 0:
              keypathstr = ','.join(keypath)
              self.logger.debug('Getting schema parameter keys for: %s', keypathstr)
              keys = list(self._search(cfg, keypathstr, *keypath, mode='getkeys'))
              if 'default' in keys:
                  keys.remove('default')
          else:
              self.logger.debug('Getting all schema parameter keys.')
              keys = list(self._allkeys(cfg))
  
          return keys
  
      ###########################################################################
      def getdict(self, *keypath, cfg=None):
          """
          Returns a schema dictionary.
  
          Searches the schema for the keypath provided and returns a complete
          dictionary. Accessing a non-existent keypath produces a logger error
          message and raises the Chip object error flag.
  
          Args:
              keypath(list str): Variable length ordered schema key list
              cfg(dict): Alternate dictionary to access in place of self.cfg
  
          Returns:
              A schema dictionary
  
          Examples:
              >>> pdk = chip.getdict('pdk')
              Returns the complete dictionary found for the keypath 'pdk'
          """
  
          if cfg is None:
              cfg = self.cfg
  
          if len(list(keypath)) > 0:
              keypathstr = ','.join(keypath)
              self.logger.debug('Getting cfg for: %s', keypathstr)
              localcfg = self._search(cfg, keypathstr, *keypath, mode='getcfg')
  
          return copy.deepcopy(localcfg)
  
      ###########################################################################
      def set(self, *args, field='value', clobber=True, cfg=None):
          '''
          Sets a schema parameter field.
  
          Sets a schema parameter field based on the keypath and value provided in
          the ``*args``. See the :ref:`Schema Reference Manual<SiliconCompiler
          Schema>` for documentation of all supported keypaths. New schema
          dictionaries are automatically created for keypaths that overlap with
          'default' dictionaries. The write action is ignored if the parameter
          value is non-empty and the clobber option is set to False.
  
          The value provided must agree with the dictionary parameter 'type'.
          Accessing a non-existent keypath or providing a value that disagrees
          with the parameter type produces a logger error message and raises the
          Chip object error flag.
  
          Args:
              args (list): Parameter keypath followed by a value to set.
              field (str): Parameter field to set.
              clobber (bool): Existing value is overwritten if True.
              cfg(dict): Alternate dictionary to access in place of self.cfg
  
          Examples:
              >>> chip.set('design', 'top')
              Sets the name of the design to 'top'
          '''
  
          if cfg is None:
              cfg = self.cfg
  
          # Verify that all keys are strings
          for key in args[:-1]:
              if not isinstance(key,str):
                  self.logger.error(f"Key [{key}] is not a string [{args}]")
  
          keypathstr = ','.join(args[:-1])
          all_args = list(args)
  
          # Special case to ensure loglevel is updated ASAP
          if len(args) == 3 and args[1] == 'loglevel' and field == 'value':
              self.logger.setLevel(args[2])
  
          self.logger.debug(f"Setting [{keypathstr}] to {args[-1]}")
          return self._search(cfg, keypathstr, *all_args, field=field, mode='set', clobber=clobber)
  
      ###########################################################################
      def add(self, *args, cfg=None, field='value'):
          '''
          Adds item(s) to a schema parameter list.
  
          Adds item(s) to schema parameter list based on the keypath and value
          provided in the ``*args``.  See the :ref:`Schema Reference
          Manual<SiliconCompiler Schema>` for documentation of all supported
          keypaths. New schema dictionaries are automatically created for keypaths
          that overlap with 'default' dictionaries.
  
          The value provided must agree with the dictionary parameter 'type'.
          Accessing a non-existent keypath, providing a value that disagrees
          with the parameter type, or using add with a scalar parameter produces
          a logger error message and raises the Chip object error flag.
  
          Args:
              args (list): Parameter keypath followed by a value to add.
              cfg(dict): Alternate dictionary to access in place of self.cfg
              field (str): Parameter field to set.
  
          Examples:
              >>> chip.add('source', 'hello.v')
              Adds the file 'hello.v' to the list of sources.
          '''
  
          if cfg is None:
              cfg = self.cfg
  
          # Verify that all keys are strings
          for key in args[:-1]:
              if not isinstance(key,str):
                  self.logger.error(f"Key [{key}] is not a string [{args}]")
  
          keypathstr = ','
  
  [... the rest is omitted ...]
files.new_path: siliconcompiler/core.py
files.old_contents: # Copyright 2020 Silicon Compiler Authors. All Rights Reserved.
  
  import argparse
  import base64
  import time
  import datetime
  import multiprocessing
  import tarfile
  import traceback
  import asyncio
  from subprocess import run, PIPE
  import os
  import glob
  import pathlib
  import sys
  import gzip
  import re
  import json
  import logging
  import hashlib
  import shutil
  import copy
  import importlib
  import textwrap
  import math
  import pandas
  import yaml
  import graphviz
  import time
  import uuid
  import shlex
  import platform
  import getpass
  import csv
  import distro
  import netifaces
  import webbrowser
  import packaging.version
  import packaging.specifiers
  from jinja2 import Environment, FileSystemLoader
  from pathlib import Path
  from timeit import default_timer as timer
  from siliconcompiler.client import *
  from siliconcompiler.schema import *
  from siliconcompiler.scheduler import _deferstep
  from siliconcompiler import leflib
  from siliconcompiler import utils
  from siliconcompiler import _metadata
  import psutil
  
  class TaskStatus():
      # Could use Python 'enum' class here, but that doesn't work nicely with
      # schema.
      PENDING = 'pending'
      SUCCESS = 'success'
      ERROR = 'error'
  
  class Chip:
      """Object for configuring and executing hardware design flows.
  
      This is the main object used for configuration, data, and
      execution within the SiliconCompiler platform.
  
      Args:
          design (string): Name of the top level chip design module.
  
      Examples:
          >>> siliconcompiler.Chip(design="top")
          Creates a chip object with name "top".
      """
  
      ###########################################################################
      def __init__(self, design, loglevel=None):
          # version numbers
          self.scversion = _metadata.version
          self.schemaversion = SCHEMA_VERSION
  
          # Local variables
          self.scroot = os.path.dirname(os.path.abspath(__file__))
          self.cwd = os.getcwd()
          self.error = 0
          self.cfg = schema_cfg()
          # The 'status' dictionary can be used to store ephemeral config values.
          # Its contents will not be saved, and can be set by parent scripts
          # such as a web server or supervisor process. Currently supported keys:
          # * 'jobhash': A hash or UUID which can identify jobs in a larger system.
          # * 'remote_cfg': Dictionary containing remote server configurations
          #                 (address, credentials, etc.)
          # * 'slurm_account': User account ID in a connected slurm HPC cluster.
          # * 'slurm_partition': Name of the partition in which a task should run
          #                      on a connected slurm HPC cluster.
          # * 'watchdog': Activity-monitoring semaphore for jobs scheduled on an
          #               HPC cluster; expects a 'threading.Event'-like object.
          # * 'max_fs_bytes': A limit on how much disk space a job is allowed
          #                   to consume in a connected HPC cluster's storage.
          self.status = {}
  
          self.builtin = ['minimum','maximum',
                          'nop', 'mux', 'join', 'verify']
  
          # We set 'design' and 'loglevel' directly in the config dictionary
          # because of a chicken-and-egg problem: self.set() relies on the logger,
          # but the logger relies on these values.
          self.cfg['design']['value'] = design
          if loglevel:
              self.cfg['option']['loglevel']['value'] = loglevel
  
          self._init_logger()
  
          self._loaded_modules = {
              'flows': [],
              'pdks': [],
              'libs': [],
              'checklists': []
          }
  
      ###########################################################################
      @property
      def design(self):
          '''Design name of chip object.
  
          This is an immutable property.'''
          return self.get('design')
  
      ###########################################################################
      def _init_logger(self, step=None, index=None, in_run=False):
  
          self.logger = logging.getLogger(uuid.uuid4().hex)
  
          # Don't propagate log messages to "root" handler (we get duplicate
          # messages without this)
          # TODO: this prevents us from being able to capture logs with pytest:
          # we should revisit it
          self.logger.propagate = False
  
          loglevel = self.get('option', 'loglevel')
  
          if loglevel=='DEBUG':
              prefix = '| %(levelname)-7s | %(funcName)-10s | %(lineno)-4s'
          else:
              prefix = '| %(levelname)-7s'
  
          if in_run:
              flow = self.get('option', 'flow')
  
              # Figure out how wide to make step and index fields
              max_step_len = 2
              max_index_len = 2
              for future_step in self.getkeys('flowgraph', flow):
                  max_step_len = max(len(future_step) + 1, max_step_len)
                  for future_index in self.getkeys('flowgraph', flow, future_step):
                      max_index_len = max(len(future_index) + 1, max_index_len)
  
              jobname = self.get('option', 'jobname')
  
              if step is None:
                  step = '-' * max(max_step_len // 4, 1)
              if index is None:
                  index = '-' * max(max_index_len // 4, 1)
  
              run_info = f'%s  | %-{max_step_len}s | %-{max_index_len}s' % (jobname, step, index)
              logformat = ' | '.join([prefix, run_info, '%(message)s'])
          else:
              logformat = ' | '.join([prefix, '%(message)s'])
  
          handler = logging.StreamHandler()
          formatter = logging.Formatter(logformat)
  
          handler.setFormatter(formatter)
  
          # Clear any existing handlers so we don't end up with duplicate messages
          # if repeat calls to _init_logger are made
          if len(self.logger.handlers) > 0:
              self.logger.handlers.clear()
  
          self.logger.addHandler(handler)
          self.logger.setLevel(loglevel)
  
      ###########################################################################
      def _deinit_logger(self):
          self.logger = None
  
      ###########################################################################
      def _get_switches(self, *keypath):
          '''Helper function for parsing switches and metavars for a keypath.'''
          #Switch field fully describes switch format
          switch = self.get(*keypath, field='switch')
  
          if switch is None:
              switches = []
          elif isinstance(switch, list):
              switches = switch
          else:
              switches = [switch]
          switchstrs = []
  
          # parse out switch from metavar
          # TODO: should we validate that metavar matches for each switch?
          for switch in switches:
              switchmatch = re.match(r'(-[\w_]+)\s+(.*)', switch)
              gccmatch = re.match(r'(-[\w_]+)(.*)', switch)
              plusmatch = re.match(r'(\+[\w_\+]+)(.*)', switch)
  
              if switchmatch:
                  switchstr = switchmatch.group(1)
                  metavar = switchmatch.group(2)
              elif gccmatch:
                  switchstr = gccmatch.group(1)
                  metavar = gccmatch.group(2)
              elif plusmatch:
                  switchstr = plusmatch.group(1)
                  metavar = plusmatch.group(2)
              switchstrs.append(switchstr)
  
          return switchstrs, metavar
  
      ###########################################################################
      def create_cmdline(self, progname, description=None, switchlist=None, input_map=None):
          """Creates an SC command line interface.
  
          Exposes parameters in the SC schema as command line switches,
          simplifying creation of SC apps with a restricted set of schema
          parameters exposed at the command line. The order of command
          line switch settings parsed from the command line is as follows:
  
           1. loglevel
           2. fpga_partname
           3. load_target('target')
           4. read_manifest([cfg])
           5. all other switches
  
          The cmdline interface is implemented using the Python argparse package
          and the following use restrictions apply.
  
          * Help is accessed with the '-h' switch.
          * Arguments that include spaces must be enclosed with double quotes.
          * List parameters are entered individually. (ie. -y libdir1 -y libdir2)
          * For parameters with Boolean types, the switch implies "true".
          * Special characters (such as '-') must be enclosed in double quotes.
          * Compiler compatible switches include: -D, -I, -O{0,1,2,3}
          * Verilog legacy switch formats are supported: +libext+, +incdir+
  
          Args:
              progname (str): Name of program to be executed.
              description (str): Short program description.
              switchlist (list of str): List of SC parameter switches to expose
                  at the command line. By default all SC schema switches are
                  available. Parameter switches should be entered based on the
                  parameter 'switch' field in the schema. For parameters with
                  multiple switches, both will be accepted if any one is included
                  in this list.
              input_map (dict of str): Dictionary mapping file extensions to input
                  filetypes. This is used to automatically assign positional
                  source arguments to ['input', ...] keypaths based on their file
                  extension. If None, the CLI will not accept positional source
                  arguments.
  
          Examples:
              >>> chip.create_cmdline(progname='sc-show',switchlist=['-input','-cfg'])
              Creates a command line interface for 'sc-show' app.
  
              >>> chip.create_cmdline(progname='sc', input_map={'v': 'verilog'})
              All sources ending in .v will be stored in ['input', 'verilog']
          """
  
          # Argparse
          parser = argparse.ArgumentParser(prog=progname,
                                           prefix_chars='-+',
                                           formatter_class=argparse.RawDescriptionHelpFormatter,
                                           description=description)
  
          # Get all keys from global dictionary or override at command line
          allkeys = self.getkeys()
  
          # Iterate over all keys to add parser arguments
          for keypath in allkeys:
              #Fetch fields from leaf cell
              helpstr = self.get(*keypath, field='shorthelp')
              typestr = self.get(*keypath, field='type')
  
              # argparse 'dest' must be a string, so join keypath with commas
              dest = '_'.join(keypath)
  
              switchstrs, metavar = self._get_switches(*keypath)
  
              # Three switch types (bool, list, scalar)
              if not switchlist or any(switch in switchlist for switch in switchstrs):
                  if typestr == 'bool':
                      parser.add_argument(*switchstrs,
                                          nargs='?',
                                          metavar=metavar,
                                          dest=dest,
                                          const='true',
                                          help=helpstr,
                                          default=argparse.SUPPRESS)
                  #list type arguments
                  elif re.match(r'\[', typestr):
                      #all the rest
                      parser.add_argument(*switchstrs,
                                          metavar=metavar,
                                          dest=dest,
                                          action='append',
                                          help=helpstr,
                                          default=argparse.SUPPRESS)
                  else:
                      #all the rest
                      parser.add_argument(*switchstrs,
                                          metavar=metavar,
                                          dest=dest,
                                          help=helpstr,
                                          default=argparse.SUPPRESS)
  
          if input_map is not None:
              parser.add_argument('source',
                                  nargs='*',
                                  help='Input files with filetype inferred by extension')
  
          #Preprocess sys.argv to enable linux commandline switch formats
          #(gcc, verilator, etc)
          scargs = []
  
          # Iterate from index 1, otherwise we end up with script name as a
          # 'source' positional argument
          for item in sys.argv[1:]:
              #Split switches with one character and a number after (O0,O1,O2)
              opt = re.match(r'(\-\w)(\d+)', item)
              #Split assign switches (-DCFG_ASIC=1)
              assign = re.search(r'(\-\w)(\w+\=\w+)', item)
              #Split plusargs (+incdir+/path)
              plusarg = re.search(r'(\+\w+\+)(.*)', item)
              if opt:
                  scargs.append(opt.group(1))
                  scargs.append(opt.group(2))
              elif plusarg:
                  scargs.append(plusarg.group(1))
                  scargs.append(plusarg.group(2))
              elif assign:
                  scargs.append(assign.group(1))
                  scargs.append(assign.group(2))
              else:
                  scargs.append(item)
  
          parser.add_argument('-version', action='version', version=_metadata.version)
  
          #Grab argument from pre-process sysargs
          cmdargs = vars(parser.parse_args(scargs))
  
          # Print banner
          print(_metadata.banner)
          print("Authors:", ", ".join(_metadata.authors))
          print("Version:", _metadata.version, "\n")
          print("-"*80)
  
          os.environ["COLUMNS"] = '80'
  
          # 1. set loglevel if set at command line
          if 'option_loglevel' in cmdargs.keys():
              self.logger.setLevel(cmdargs['option_loglevel'])
  
          # 2. read in target if set
          if 'option_target' in cmdargs.keys():
              if 'arg_pdk' in cmdargs.keys():
                  raise NotImplementedError("NOT IMPLEMENTED: ['arg', 'pdk'] parameter with target")
              if 'arg_flow' in cmdargs.keys():
                  raise NotImplementedError("NOT IMPLEMENTED: ['arg', 'flow'] parameter with target")
              if 'fpga_partname' in cmdargs.keys():
                  self.set('fpga', 'partname', cmdargs['fpga_partname'], clobber=True)
              # running target command
              self.load_target(cmdargs['option_target'])
  
          # 4. read in all cfg files
          if 'option_cfg' in cmdargs.keys():
              for item in cmdargs['option_cfg']:
                  self.read_manifest(item, clobber=True, clear=True)
  
          # Map sources to ['input'] keypath.
          if 'source' in cmdargs:
              for source in cmdargs['source']:
                  _, ext = os.path.splitext(source)
                  ext = ext.lstrip('.')
                  if ext in input_map:
                      filetype = input_map[ext]
                      if self.valid('input', filetype, quiet=True):
                          self.add('input', filetype, source)
                      else:
                          self.set('input', filetype, source)
                      self.logger.info(f'Source {source} inferred as {filetype}')
                  else:
                      self.logger.warning('Unable to infer input type for '
                          f'{source} based on file extension, ignoring. Use the '
                          '-input flag to provide it explicitly.')
              # we don't want to handle this in the next loop
              del cmdargs['source']
  
          # 5. Cycle through all command args and write to manifest
          for dest, vals in cmdargs.items():
              keypath = dest.split('_')
  
              # Turn everything into a list for uniformity
              if not isinstance(vals, list):
                  vals = [vals]
  
              # Cycle through all items
              for item in vals:
                  # Hack to handle the fact that we want optmode stored with an 'O'
                  # prefix.
                  if keypath == ['option', 'optmode']:
                      item = 'O' + item
  
                  num_free_keys = keypath.count('default')
  
                  if len(item.split(' ')) < num_free_keys + 1:
                      # Error out if value provided doesn't have enough words to
                      # fill in 'default' keys.
                      switches, metavar = self._get_switches(*keypath)
                      switchstr = '/'.join(switches)
                      self.logger.error(f'Invalid value {item} for switch {switchstr}. Expected format {metavar}.')
                      raise SiliconCompilerError('Invalid CLI arguments')
  
                  # We replace 'default' in keypath with first N words in provided
                  # value. Remainder is the actual value we want to store in the
                  # parameter.
                  *free_keys, val = item.split(' ', num_free_keys)
                  args = [free_keys.pop(0) if key == 'default' else key for key in keypath]
  
                  # Storing in manifest
                  self.logger.info(f"Command line argument entered: {args} Value: {val}")
                  typestr = self.get(*keypath, field='type')
                  if typestr.startswith('['):
                      if self.valid(*args, quiet=True):
                          self.add(*args, val)
                      else:
                          self.set(*args, val, clobber=True)
                  else:
                      self.set(*args, val, clobber=True)
  
      #########################################################################
      def find_function(self, modulename, funcname, moduletype=None):
          '''
          Returns a function attribute from a module on disk.
  
          Searches the SC root directory and the 'scpath' parameter for the
          modulename provided and imports the module if found. If the funcname
          provided is found in the module, a callable function attribute is
          returned, otherwise None is returned.
  
          The function assumes the following directory structure:
  
          * tools/modulename/modulename.py
          * flows/modulename.py
          * pdks/modulname.py
  
          If the moduletype is None, the module paths are search in the
          order: 'targets'->'flows'->'tools'->'pdks'->'libs'->'checklists'):
  
  
          Supported functions include:
  
          * targets (make_docs, setup)
          * pdks (make_docs, setup)
          * flows (make_docs, setup)
          * tools (make_docs, setup, check_version, runtime_options,
            pre_process, post_process)
          * libs (make_docs, setup)
  
          Args:
              modulename (str): Name of module to import.
              funcname (str): Name of the function to find within the module.
              moduletype (str): Type of module (flows, pdks, libs, checklists, targets).
  
          Examples:
              >>> setup_pdk = chip.find_function('freepdk45', 'setup', 'pdks')
              >>> setup_pdk()
              Imports the freepdk45 module and runs the setup_pdk function
  
          '''
  
          # module search path depends on modtype
          if moduletype is None:
              for item in ('targets', 'flows', 'tools', 'pdks', 'libs', 'checklists'):
                  fullpath = self.find_function(modulename, funcname, module_type=item)
                  if fullpath:
                      break
                  self.logger.error(f"Could not find module {modulename}")
                  self.error = 1
                  return None
          elif moduletype in ('targets','flows', 'pdks', 'libs'):
              fullpath = self._find_sc_file(f"{moduletype}/{modulename}.py", missing_ok=True)
          elif moduletype in ('tools', 'checklists'):
              fullpath = self._find_sc_file(f"{moduletype}/{modulename}/{modulename}.py", missing_ok=True)
          else:
              self.logger.error(f"Illegal module type '{moduletype}'.")
              self.error = 1
              return
  
          # try loading module if found
          if fullpath:
              self.logger.debug(f"Loading function '{funcname}' from module '{modulename}'")
  
              try:
                  spec = importlib.util.spec_from_file_location(modulename, fullpath)
                  imported = importlib.util.module_from_spec(spec)
                  spec.loader.exec_module(imported)
  
                  if hasattr(imported, funcname):
                      function = getattr(imported, funcname)
                  else:
                      function = None
                  return function
              except:
                  traceback.print_exc()
                  self.logger.error(f"Module setup failed for '{modulename}'")
                  self.error = 1
  
      ##########################################################################
      def load_target(self, name):
          """
          Loads a target module and runs the setup() function.
  
          The function searches the $SCPATH for targets/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
              flow (str): Target flow to
  
          Examples:
              >>> chip.load_target('freepdk45_demo')
              Loads the 'freepdk45_demo' target
  
          """
  
          self.set('option', 'target', name)
  
          func = self.find_function(name, 'setup', 'targets')
          if func is not None:
              func(self)
          else:
              self.logger.error(f'Target module {name} not found in $SCPATH or siliconcompiler/targets/.')
              raise SiliconCompilerError(f'Target module {name} not found $SCPATH or siliconcompiler/targets/.')
  
      ##########################################################################
      def load_pdk(self, name):
          """
          Loads a PDK module and runs the setup() function.
  
          The function searches the $SCPATH for pdks/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
  
          Examples:
              >>> chip.load_pdk('freepdk45_pdk')
              Loads the 'freepdk45' pdk
  
          """
  
          func = self.find_function(name, 'setup', 'pdks')
          if func is not None:
              self.logger.info(f"Loading PDK '{name}'")
              self._loaded_modules['pdks'].append(name)
              func(self)
          else:
              self.logger.error(f'PDK module {name} not found in $SCPATH or siliconcompiler/pdks/.')
              raise SiliconCompilerError(f'PDK module {name} not found in $SCPATH or siliconcompiler/pdks/.')
  
      ##########################################################################
      def load_flow(self, name):
          """
          Loads a flow  module and runs the setup() function.
  
          The function searches the $SCPATH for flows/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
  
          Examples:
              >>> chip.load_flow('asicflow')
              Loads the 'asicflow' flow
  
          """
  
          func = self.find_function(name, 'setup', 'flows')
          if func is not None:
              self.logger.info(f"Loading flow '{name}'")
              self._loaded_modules['flows'].append(name)
              func(self)
          else:
              self.logger.error(f'Flow module {name} not found in $SCPATH or siliconcompiler/flows/.')
              raise SiliconCompilerError(f'Flow module {name} not found in $SCPATH or siliconcompiler/flows/.')
  
      ##########################################################################
      def load_lib(self, name):
          """
          Loads a library module and runs the setup() function.
  
          The function searches the $SCPATH for libs/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
  
          Examples:
              >>> chip.load_lib('nangate45')
              Loads the 'nangate45' library
  
          """
  
          func = self.find_function(name, 'setup', 'libs')
          if func is not None:
              self.logger.info(f"Loading library '{name}'")
              self._loaded_modules['libs'].append(name)
              func(self)
          else:
              self.logger.error(f'Library module {name} not found in $SCPATH or siliconcompiler/libs/.')
              raise SiliconCompilerError(f'Library module {name} not found in $SCPATH or siliconcompiler/libs/.')
  
      ##########################################################################
      def load_checklist(self, name):
          """
          Loads a checklist module and runs the setup() function.
  
          The function searches the $SCPATH for checklist/<name>/<name>.py and runs
          the setup function in that module if found.
  
          Args:
              name (str): Module name
  
          Examples:
              >>> chip.load_checklist('oh_tapeout')
              Loads the 'oh_tapeout' checklist
  
          """
  
          func = self.find_function(name, 'setup', 'checklists')
          if func is not None:
              self.logger.info(f"Loading checklist '{name}'")
              self._loaded_modules['checklists'].append(name)
              func(self)
          else:
              self.logger.error(f'Checklist module {name} not found in $SCPATH or siliconcompiler/checklists/.')
              raise SiliconCompilerError(f'Checklist module {name} not found in $SCPATH or siliconcompiler/checklists/.')
  
  
      ###########################################################################
      def list_metrics(self):
          '''
          Returns a list of all metrics in the schema.
  
          '''
  
          return self.getkeys('metric','default','default')
  
      ###########################################################################
      def help(self, *keypath):
          """
          Returns a schema parameter description.
  
          Args:
              *keypath(str): Keypath to parameter.
  
          Returns:
              A formatted multi-line help paragraph for the parameter provided.
  
          Examples:
              >>> print(chip.help('asic','diearea'))
              Displays help information about the 'asic, diearea' parameter
  
          """
  
          self.logger.debug('Fetching help for %s', keypath)
  
          #Fetch Values
  
          description = self.get(*keypath, field='shorthelp')
          typestr = self.get(*keypath, field='type')
          switchstr = str(self.get(*keypath, field='switch'))
          defstr = str(self.get(*keypath, field='defvalue'))
          requirement = str(self.get(*keypath, field='require'))
          helpstr = self.get(*keypath, field='help')
          example = self.get(*keypath, field='example')
  
          examplestr = ("\nExamples:    " + example[0] + ''.join(
                       ["\n             " + ex for ex in example[1:]]))
  
          #Removing multiple spaces and newlines
          helpstr = helpstr.rstrip()
          helpstr = helpstr.replace("\n", "")
          helpstr = ' '.join(helpstr.split())
  
          for idx, item in enumerate(example):
              example[idx] = ' '.join(item.split())
              example[idx] = example[idx].replace(", ", ",")
  
          #Wrap text
          para = textwrap.TextWrapper(width=60)
          para_list = para.wrap(text=helpstr)
  
          #Full Doc String
          fullstr = ("-"*80 +
                     "\nDescription: " + description +
                     "\nSwitch:      " + switchstr +
                     "\nType:        " + typestr  +
                     "\nRequirement: " + requirement   +
                     "\nDefault:     " + defstr   +
                     examplestr +
                     "\nHelp:        " + para_list[0] + "\n")
          for line in para_list[1:]:
              fullstr = (fullstr +
                         " "*13 + line.lstrip() + "\n")
  
          return fullstr
  
  
      ###########################################################################
      def valid(self, *args, valid_keypaths=None, quiet=True, default_valid=False):
          """
          Checks validity of a keypath.
  
          Checks the validity of a parameter keypath and returns True if the
          keypath is valid and False if invalid.
  
          Args:
              keypath(list str): Variable length schema key list.
              valid_keypaths (list of list): List of valid keypaths as lists. If
                  None, check against all keypaths in the schema.
              quiet (bool): If True, don't display warnings for invalid keypaths.
  
          Returns:
              Boolean indicating validity of keypath.
  
          Examples:
              >>> check = chip.valid('design')
              Returns True.
              >>> check = chip.valid('blah')
              Returns False.
          """
  
          keypathstr = ','.join(args)
          keylist = list(args)
          if default_valid:
              default = 'default'
          else:
              default = None
  
          if valid_keypaths is None:
              valid_keypaths = self.getkeys()
  
          # Look for a full match with default playing wild card
          for valid_keypath in valid_keypaths:
              if len(keylist) != len(valid_keypath):
                  continue
  
              ok = True
              for i in range(len(keylist)):
                  if valid_keypath[i] not in (keylist[i], default):
                      ok = False
                      break
              if ok:
                  return True
  
          # Match not found
          if not quiet:
              self.logger.warning(f"Keypath [{keypathstr}] is not valid")
          return False
  
      ###########################################################################
      def get(self, *keypath, field='value', job=None, cfg=None):
          """
          Returns a schema parameter field.
  
          Returns a schema parameter field based on the keypath provided in the
          ``*keypath``. See the :ref:`Schema Reference Manual<SiliconCompiler
          Schema>` for documentation of all supported keypaths. The returned type
          is consistent with the type field of the parameter. Fetching parameters
          with empty or undefined value files returns None for scalar types and []
          (empty list) for list types.  Accessing a non-existent keypath produces
          a logger error message and raises the Chip object error flag.
  
          Args:
              keypath(list str): Variable length schema key list.
              field(str): Parameter field to fetch.
              job (str): Jobname to use for dictionary access in place of the
                  current active jobname.
              cfg(dict): Alternate dictionary to access in place of the default
                  chip object schema dictionary.
  
          Returns:
              Value found for the keypath and field provided.
  
          Examples:
              >>> foundry = chip.get('pdk', 'foundry')
              Returns the name of the foundry from the PDK.
  
          """
  
          if cfg is None:
              if job is not None:
                  cfg = self.cfg['history'][job]
              else:
                  cfg = self.cfg
  
          keypathstr = ','.join(keypath)
  
          self.logger.debug(f"Reading from [{keypathstr}]. Field = '{field}'")
          return self._search(cfg, keypathstr, *keypath, field=field, mode='get')
  
      ###########################################################################
      def getkeys(self, *keypath, cfg=None, job=None):
          """
          Returns a list of schema dictionary keys.
  
          Searches the schema for the keypath provided and returns a list of
          keys found, excluding the generic 'default' key. Accessing a
          non-existent keypath produces a logger error message and raises the
          Chip object error flag.
  
          Args:
              keypath (list str): Variable length ordered schema key list
              cfg (dict): Alternate dictionary to access in place of self.cfg
              job (str): Jobname to use for dictionary access in place of the
                  current active jobname.
  
          Returns:
              List of keys found for the keypath provided.
  
          Examples:
              >>> keylist = chip.getkeys('pdk')
              Returns all keys for the 'pdk' keypath.
              >>> keylist = chip.getkeys()
              Returns all list of all keypaths in the schema.
          """
  
          if cfg is None:
              if job is None:
                  cfg = self.cfg
              else:
                  cfg = self.cfg['history'][job]
  
          if len(list(keypath)) > 0:
              keypathstr = ','.join(keypath)
              self.logger.debug('Getting schema parameter keys for: %s', keypathstr)
              keys = list(self._search(cfg, keypathstr, *keypath, mode='getkeys'))
              if 'default' in keys:
                  keys.remove('default')
          else:
              self.logger.debug('Getting all schema parameter keys.')
              keys = list(self._allkeys(cfg))
  
          return keys
  
      ###########################################################################
      def getdict(self, *keypath, cfg=None):
          """
          Returns a schema dictionary.
  
          Searches the schema for the keypath provided and returns a complete
          dictionary. Accessing a non-existent keypath produces a logger error
          message and raises the Chip object error flag.
  
          Args:
              keypath(list str): Variable length ordered schema key list
              cfg(dict): Alternate dictionary to access in place of self.cfg
  
          Returns:
              A schema dictionary
  
          Examples:
              >>> pdk = chip.getdict('pdk')
              Returns the complete dictionary found for the keypath 'pdk'
          """
  
          if cfg is None:
              cfg = self.cfg
  
          if len(list(keypath)) > 0:
              keypathstr = ','.join(keypath)
              self.logger.debug('Getting cfg for: %s', keypathstr)
              localcfg = self._search(cfg, keypathstr, *keypath, mode='getcfg')
  
          return copy.deepcopy(localcfg)
  
      ###########################################################################
      def set(self, *args, field='value', clobber=True, cfg=None):
          '''
          Sets a schema parameter field.
  
          Sets a schema parameter field based on the keypath and value provided in
          the ``*args``. See the :ref:`Schema Reference Manual<SiliconCompiler
          Schema>` for documentation of all supported keypaths. New schema
          dictionaries are automatically created for keypaths that overlap with
          'default' dictionaries. The write action is ignored if the parameter
          value is non-empty and the clobber option is set to False.
  
          The value provided must agree with the dictionary parameter 'type'.
          Accessing a non-existent keypath or providing a value that disagrees
          with the parameter type produces a logger error message and raises the
          Chip object error flag.
  
          Args:
              args (list): Parameter keypath followed by a value to set.
              field (str): Parameter field to set.
              clobber (bool): Existing value is overwritten if True.
              cfg(dict): Alternate dictionary to access in place of self.cfg
  
          Examples:
              >>> chip.set('design', 'top')
              Sets the name of the design to 'top'
          '''
  
          if cfg is None:
              cfg = self.cfg
  
          # Verify that all keys are strings
          for key in args[:-1]:
              if not isinstance(key,str):
                  self.logger.error(f"Key [{key}] is not a string [{args}]")
  
          keypathstr = ','.join(args[:-1])
          all_args = list(args)
  
          # Special case to ensure loglevel is updated ASAP
          if len(args) == 3 and args[1] == 'loglevel' and field == 'value':
              self.logger.setLevel(args[2])
  
          self.logger.debug(f"Setting [{keypathstr}] to {args[-1]}")
          return self._search(cfg, keypathstr, *all_args, field=field, mode='set', clobber=clobber)
  
      ###########################################################################
      def add(self, *args, cfg=None, field='value'):
          '''
          Adds item(s) to a schema parameter list.
  
          Adds item(s) to schema parameter list based on the keypath and value
          provided in the ``*args``.  See the :ref:`Schema Reference
          Manual<SiliconCompiler Schema>` for documentation of all supported
          keypaths. New schema dictionaries are automatically created for keypaths
          that overlap with 'default' dictionaries.
  
          The value provided must agree with the dictionary parameter 'type'.
          Accessing a non-existent keypath, providing a value that disagrees
          with the parameter type, or using add with a scalar parameter produces
          a logger error message and raises the Chip object error flag.
  
          Args:
              args (list): Parameter keypath followed by a value to add.
              cfg(dict): Alternate dictionary to access in place of self.cfg
              field (str): Parameter field to set.
  
          Examples:
              >>> chip.add('source', 'hello.v')
              Adds the file 'hello.v' to the list of sources.
          '''
  
          if cfg is None:
              cfg = self.cfg
  
          # Verify that all keys are strings
          for key in args[:-1]:
              if not isinstance(key,str):
                  self.logger.error(f"Key [{key}] is not a string [{args}]")
  
          keypathstr = ','
  
  [... the rest is omitted ...]
files.old_path: siliconcompiler/core.py
files.new_contents: # Copyright 2020 Silicon Compiler Authors. All Rights Reserved.
  
  from siliconcompiler import utils
  
  import re
  import os
  import sys
  import copy as pycopy
  import json
  
  SCHEMA_VERSION = '0.9.0'
  
  #############################################################################
  # PARAM DEFINITION
  #############################################################################
  
  def scparam(cfg,
              keypath,
              sctype=None,
              require=None,
              defvalue=None,
              scope='job',
              copy='false',
              lock='false',
              hashalgo='sha256',
              signature=None,
              notes=None,
              unit=None,
              shorthelp=None,
              switch=None,
              example=None,
              schelp=None):
  
      # 1. decend keypath until done
      # 2. create key if missing
      # 3. populate leaf cell when keypath empty
      if keypath:
          key = keypath[0]
          keypath.pop(0)
          if key not in cfg.keys():
              cfg[key] = {}
          scparam(cfg[key],
                  keypath,
                  sctype=sctype,
                  scope=scope,
                  require=require,
                  defvalue=defvalue,
                  copy=copy,
                  lock=lock,
                  hashalgo=hashalgo,
                  signature=signature,
                  notes=notes,
                  unit=unit,
                  shorthelp=shorthelp,
                  switch=switch,
                  example=example,
                  schelp=schelp)
      else:
  
          # removing leading spaces as if schelp were a docstring
          schelp = utils.trim(schelp)
  
          # setting valus based on types
          # note (bools are never lists)
          if re.match(r'bool',sctype):
              require = 'all'
              if defvalue is None:
                  defvalue = 'false'
          if re.match(r'\[',sctype) and signature is None:
              signature = []
          if re.match(r'\[',sctype) and defvalue is None:
              defvalue = []
  
          # mandatory for all
          cfg['defvalue'] = defvalue
          cfg['value'] = pycopy.copy(defvalue)
          cfg['type'] = sctype
          cfg['scope'] = scope
          cfg['require'] = require
          cfg['lock'] = lock
          cfg['switch'] = switch
          cfg['shorthelp'] = shorthelp
          cfg['example'] = example
          cfg['help'] = schelp
          cfg['signature'] = signature
          cfg['notes'] = notes
  
          # unit for floats/ints
          if unit is not None:
              cfg['unit'] = unit
  
          # file only values
          if re.search(r'file',sctype):
              cfg['hashalgo'] = hashalgo
              cfg['copy'] = copy
              cfg['filehash'] = []
              cfg['date'] = []
              cfg['author'] = []
  
  
  #############################################################################
  # CHIP CONFIGURATION
  #############################################################################
  
  def schema_cfg():
      '''Method for defining Chip configuration schema
      All the keys defined in this dictionary are reserved words.
      '''
  
      # SC version number (bump on every non trivial change)
      # Version number following semver standard.
      # Software version syncs with SC releases (from _metadata)
  
      SCHEMA_VERSION = '0.8.0'
  
      # Basic schema setup
      cfg = {}
  
      # Place holder dictionaries updated by core methods()
      cfg['history'] = {}
      cfg['library'] = {}
  
      scparam(cfg,['schemaversion'],
              sctype='str',
              scope='global',
              defvalue=SCHEMA_VERSION,
              require='all',
              shorthelp="Schema version number",
              lock='true',
              switch="-schemaversion <str>",
              example=["api: chip.get('schemaversion')"],
              schelp="""SiliconCompiler schema version number.""")
  
      # Design topmodule/entrypoint
      scparam(cfg,['design'],
              sctype='str',
              scope='global',
              require='all',
              shorthelp="Design top module name",
              switch="-design <str>",
              example=["cli: -design hello_world",
                      "api: chip.set('design', 'hello_world')"],
              schelp="""Name of the top level module or library. Required for all
              chip objects.""")
  
      # input/output
      io = {'input': ['Input','true'],
            'output': ['Output','false']
      }
  
      filetype = 'default'
      for item, val in io.items():
          scparam(cfg,[item, filetype],
                  sctype='[file]',
                  copy=f"{val[1]}",
                  shorthelp=f"{val[0]} files",
                  switch=f"-{item} 'filetype <file>'",
                  example=[
                      f"cli: -{item} 'verilog hello_world.v'",
                      f"api: chip.set({item},'verilog','hello_world.v')"],
                  schelp=f"""
                  List of {item} files specifed by type. The filetype name must
                  align with the parameter names within the flow and tool setup
                  scripts. Examples of acceptable file types include python, c,
                  systemc, verilog, vhdl, netlist, def, gds, gerber, saif, sdc,
                  saif, vcd, spef, sdf.""")
  
      # Inputs and constraints
      cfg = schema_constraint(cfg)
  
      # Options
      cfg = schema_option(cfg)
      cfg = schema_arg(cfg)
      cfg = schema_unit(cfg)
  
      # Technology configuration
      cfg = schema_fpga(cfg)
      cfg = schema_asic(cfg)
      cfg = schema_pdk(cfg)
  
      # Flows
      cfg = schema_tool(cfg)
      cfg = schema_flowgraph(cfg)
  
      # Metrics
      cfg = schema_checklist(cfg)
      cfg = schema_metric(cfg)
      cfg = schema_record(cfg)
  
      # Modeling
      cfg = schema_model(cfg)
  
      # Datasheeet
      cfg = schema_datasheet(cfg)
  
      # Packaging
      cfg = schema_package(cfg)
  
      return cfg
  
  ###############################################################################
  # FPGA
  ###############################################################################
  
  def schema_fpga(cfg):
      ''' FPGA configuration
      '''
  
      scparam(cfg,['fpga', 'arch'],
              sctype='[file]',
              copy='true',
              shorthelp="FPGA: architecture file",
              switch="-fpga_arch <file>",
              example=["cli: -fpga_arch myfpga.xml",
                       "api:  chip.set('fpga', 'arch', 'myfpga.xml')"],
              schelp=""" Architecture definition file for FPGA place and route
              tool. For the VPR tool, the file is a required XML based description,
              allowing targeting a large number of virtual and commercial
              architectures. For most commercial tools, the fpga part name provides
              enough information to enable compilation and the 'arch' parameter is
              optional.""")
  
      scparam(cfg,['fpga', 'vendor'],
              sctype='str',
              shorthelp="FPGA: vendor name",
              switch="-fpga_vendor <str>",
              example=["cli: -fpga_vendor acme",
                      "api:  chip.set('fpga', 'vendor', 'acme')"],
              schelp="""
              Name of the FPGA vendor. The parameter is used to check part
              name and to select the eda tool flow in case 'edaflow' is
              unspecified.""")
  
      scparam(cfg,['fpga', 'partname'],
              sctype='str',
              require='fpga',
              shorthelp="FPGA: part name",
              switch="-fpga_partname <str>",
              example=["cli: -fpga_partname fpga64k",
                       "api:  chip.set('fpga', 'partname', 'fpga64k')"],
              schelp="""
              Complete part name used as a device target by the FPGA compilation
              tool. The part name must be an exact string match to the partname
              hard coded within the FPGA eda tool.""")
  
      scparam(cfg,['fpga', 'board'],
              sctype='str',
              shorthelp="FPGA: board name",
              switch="-fpga_board <str>",
              example=["cli: -fpga_board parallella",
                       "api:  chip.set('fpga', 'board', 'parallella')"],
              schelp="""
              Complete board name used as a device target by the FPGA compilation
              tool. The board name must be an exact string match to the partname
              hard coded within the FPGA eda tool. The parameter is optional and can
              be used in place of a partname and pin constraints for some tools.""")
  
      scparam(cfg,['fpga', 'program'],
              sctype='bool',
              shorthelp="FPGA: program enable",
              switch="-fpga_program <bool>",
              example=["cli: -fpga_program",
                       "api:  chip.set('fpga', 'program', True)"],
              schelp="""Specifies that the bitstream should be loaded into an FPGA.""")
  
      scparam(cfg,['fpga', 'flash'],
              sctype='bool',
              shorthelp="FPGA: flash enable",
              switch="-fpga_flash <bool>",
              example=["cli: -fpga_flash",
                       "api:  chip.set('fpga', 'flash', True)"],
              schelp="""Specifies that the bitstream should be flashed in the board/device.
              The default is to load the bitstream into volatile memory (SRAM).""")
  
      return cfg
  
  ###############################################################################
  # PDK
  ###############################################################################
  
  def schema_pdk(cfg, stackup='default'):
      ''' Process design kit configuration
      '''
  
      tool = 'default'
      filetype = 'default'
      pdkname = 'default'
  
      scparam(cfg, ['pdk', pdkname, 'foundry'],
              sctype='str',
              scope='global',
              require="asic",
              shorthelp="PDK: foundry name",
              switch="-pdk_foundry 'pdkname <str>'",
              example=["cli: -pdk_foundry 'asap7 virtual'",
                      "api:  chip.set('pdk', 'asap7', 'foundry', 'virtual')"],
              schelp="""
              Name of foundry corporation. Examples include intel, gf, tsmc,
              samsung, skywater, virtual. The \'virtual\' keyword is reserved for
              simulated non-manufacturable processes.""")
  
      scparam(cfg, ['pdk', pdkname, 'node'],
              sctype='float',
              scope='global',
              require="asic",
              shorthelp="PDK: process node",
              switch="-pdk_node 'pdkname <float>'",
              example=["cli: -pdk_node 'asap7 130'",
                      "api:  chip.set('pdk', 'asap7', 'node', 130)"],
              schelp="""
              Approximate relative minimum dimension of the process target specified
              in nanometers. The parameter is required for flows and tools that
              leverage the value to drive technology dependent synthesis and APR
              optimization. Node examples include 180, 130, 90, 65, 45, 32, 22 14,
              10, 7, 5, 3.""")
  
      scparam(cfg, ['pdk', pdkname, 'version'],
              sctype='str',
              scope='global',
              shorthelp="PDK: version",
              switch="-pdk_version 'pdkname <str>'",
              example=["cli: -pdk_version 'asap7 1.0'",
                      "api:  chip.set('pdk', 'asap7', 'version', '1.0')"],
              schelp="""
              Alphanumeric string specifying the version of the PDK. Verification of
              correct PDK and IP versions is a hard ASIC tapeout require in all
              commercial foundries. The version number can be used for design manifest
              tracking and tapeout checklists.""")
  
      scparam(cfg, ['pdk', pdkname, 'stackup'],
              sctype='[str]',
              scope='global',
              require='asic',
              shorthelp="PDK: metal stackups",
              switch="-pdk_stackup 'pdkname <str>'",
              example=["cli: -pdk_stackup 'asap7 2MA4MB2MC'",
                       "api: chip.add('pdk', 'asap7','stackup','2MA4MB2MC')"],
              schelp="""
              List of all metal stackups offered in the process node. Older process
              nodes may only offer a single metal stackup, while advanced nodes
              offer a large but finite list of metal stacks with varying combinations
              of metal line pitches and thicknesses. Stackup naming is unique to a
              foundry, but is generally a long string or code. For example, a 10
              metal stackup with two 1x wide, four 2x wide, and 4x wide metals,
              might be identified as 2MA4MB2MC, where MA, MB, and MC denote wiring
              layers with different properties (thickness, width, space). Each
              stackup will come with its own set of routing technology files and
              parasitic models specified in the pdk_pexmodel and pdk_aprtech
              parameters.""")
  
      scparam(cfg, ['pdk', pdkname, 'thickness', stackup],
              sctype='float',
              scope='global',
              unit='mm',
              shorthelp="PDK: unit thickness",
              switch="-pdk_thickness 'pdkname stackup <float>'",
              example=["cli: -pdk_thickness 'asap7 2MA4MB2MC 1.57'",
                      "api:  chip.set('pdk', 'asap7', 'thickness', '2MA4MB2MC', 1.57)"],
              schelp="""
              Thickness of a manfuatured unit specified on a per stackup basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'wafersize'],
              sctype='float',
              scope='global',
              unit='mm',
              require="asic",
              shorthelp="PDK: wafer size",
              switch="-pdk_wafersize 'pdkname <float>'",
              example=["cli: -pdk_wafersize 'asap7 300'",
                      "api:  chip.set('pdk', 'asap7', 'wafersize', 300)"],
              schelp="""
              Wafer diameter used in wafer based manufacturing process. The standard diameter
              for leading edge manufacturing is 300mm. For older process technologies
              and specialty fabs, smaller diameters such as 200, 100, 125, 100 are common.
              The value is used to calculate dies per wafer and full factory chip costs.""")
  
      scparam(cfg, ['pdk', pdkname, 'panelsize'],
              sctype='[(float,float)]',
              scope='global',
              unit='mm',
              shorthelp="PDK: panel size",
              switch="-pdk_panelsize 'pdkname <float>'",
              example=["cli: -pdk_panelsize 'asap7 (45.72,60.96)'",
                      "api:  chip.set('pdk', 'asap7', 'panelsize', (45.72,60.96))"],
              schelp="""
              List of panel sizes supported in the manufacturing process.
              """)
  
      scparam(cfg, ['pdk', pdkname, 'unitcost'],
              sctype='float',
              scope='global',
              unit='USD',
              shorthelp="PDK: unit cost",
              switch="-pdk_unitcost 'pdkname <float>'",
              example=["cli: -pdk_unitcost 'asap7 10000'",
                       "api:  chip.set('pdk', 'asap7', 'unitcost', 10000)"],
              schelp="""
              Raw cost per unit shipped by the factory, not accounting for yield
              loss.""")
  
      scparam(cfg, ['pdk', pdkname, 'd0'],
              sctype='float',
              scope='global',
              shorthelp="PDK: process defect density",
              switch="-pdk_d0 'pdkname <float>'",
              example=["cli: -pdk_d0 'asap7 0.1'",
                       "api:  chip.set('pdk', 'asap7', 'd0', 0.1)"],
              schelp="""
              Process defect density (d0) expressed as random defects per cm^2. The
              value is used to calculate yield losses as a function of area, which in
              turn affects the chip full factory costs. Two yield models are
              supported: Poisson (default), and Murphy. The Poisson based yield is
              calculated as dy = exp(-area * d0/100). The Murphy based yield is
              calculated as dy = ((1-exp(-area * d0/100))/(area * d0/100))^2.""")
  
      scparam(cfg, ['pdk', pdkname, 'hscribe'],
              sctype='float',
              scope='global',
              unit='mm',
              shorthelp="PDK: horizontal scribe line width",
              switch="-pdk_hscribe 'pdkname <float>'",
              example=["cli: -pdk_hscribe 'asap7 0.1'",
                       "api:  chip.set('pdk', 'asap7', 'hscribe', 0.1)"],
              schelp="""
              Width of the horizontal scribe line used during die separation.
              The process is generally completed using a mechanical saw, but can be
              done through combinations of mechanical saws, lasers, wafer thinning,
              and chemical etching in more advanced technologies. The value is used
              to calculate effective dies per wafer and full factory cost.""")
  
      scparam(cfg, ['pdk', pdkname, 'vscribe'],
              sctype='float',
              scope='global',
              unit='mm',
              shorthelp="PDK: vertical scribe line width",
              switch="-pdk_vscribe 'pdkname <float>'",
              example=["cli: -pdk_vscribe 'asap7 0.1'",
                       "api:  chip.set('pdk', 'asap7', 'vscribe', 0.1)"],
              schelp="""
               Width of the vertical scribe line used during die separation.
              The process is generally completed using a mechanical saw, but can be
              done through combinations of mechanical saws, lasers, wafer thinning,
              and chemical etching in more advanced technologies. The value is used
              to calculate effective dies per wafer and full factory cost.""")
  
      scparam(cfg, ['pdk', pdkname, 'edgemargin'],
              sctype='float',
              scope='global',
              unit='mm',
              shorthelp="PDK: wafer edge keep-out margin",
              switch="-pdk_edgemargin 'pdkname <float>'",
              example=[
                  "cli: -pdk_edgemargin 'asap7 1'",
                  "api:  chip.set('pdk', 'asap7', 'edgemargin', 1)"],
              schelp="""
              Keep-out distance/margin from the edge inwards. The edge
              is prone to chipping and need special treatment that preclude
              placement of designs in this area. The edge value is used to
              calculate effective units per wafer/panel and full factory cost.""")
  
      scparam(cfg, ['pdk', pdkname, 'density'],
              sctype='float',
              scope='global',
              shorthelp="PDK: transistor density",
              switch="-pdk_density 'pdkname <float>'",
              example=["cli: -pdk_density 'asap7 100e6'",
                      "api:  chip.set('pdk', 'asap7', 'density', 10e6)"],
              schelp="""
              Approximate logic density expressed as # transistors / mm^2
              calculated as:
              0.6 * (Nand2 Transistor Count) / (Nand2 Cell Area) +
              0.4 * (Register Transistor Count) / (Register Cell Area)
              The value is specified for a fixed standard cell library within a node
              and will differ depending on the library vendor, library track height
              and library type. The value can be used to to normalize the effective
              density reported for the design across different process nodes. The
              value can be derived from a variety of sources, including the PDK DRM,
              library LEFs, conference presentations, and public analysis.""")
  
      simtype = 'default'
      scparam(cfg, ['pdk', pdkname, 'devmodel', tool, simtype, stackup],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: device models",
              switch="-pdk_devmodel 'pdkname tool simtype stackup <file>'",
              example=[
              "cli: -pdk_devmodel 'asap7 xyce spice M10 asap7.sp'",
              "api: chip.set('pdk','asap7','devmodel','xyce','spice','M10','asap7.sp')"],
              schelp="""
              List of filepaths to PDK device models for different simulation
              purposes and for different tools. Examples of device model types
              include spice, aging, electromigration, radiation. An example of a
              'spice' tool is xyce. Device models are specified on a per metal stack
              basis. Process nodes with a single device model across all stacks will
              have a unique parameter record per metal stack pointing to the same
              device model file.  Device types and tools are dynamic entries
              that depend on the tool setup and device technology. Pseud-standardized
              device types include spice, em (electromigration), and aging.""")
  
      corner='default'
      scparam(cfg, ['pdk', pdkname, 'pexmodel', tool, stackup, corner],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: parasitic TCAD models",
              switch="-pdk_pexmodel 'pdkname tool stackup corner <file>'",
              example=[
                  "cli: -pdk_pexmodel 'asap7 fastcap M10 max wire.mod'",
                  "api: chip.set('pdk','asap7','pexmodel','fastcap','M10','max','wire.mod')"],
              schelp="""
              List of filepaths to PDK wire TCAD models used during automated
              synthesis, APR, and signoff verification. Pexmodels are specified on
              a per metal stack basis. Corner values depend on the process being
              used, but typically include nomenclature such as min, max, nominal.
              For exact names, refer to the DRM. Pexmodels are generally not
              standardized and specified on a per tool basis. An example of pexmodel
              type is 'fastcap'.""")
  
      src = 'default'
      dst = 'default'
      scparam(cfg, ['pdk', pdkname, 'layermap', tool, src, dst, stackup],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: layer map file",
              switch="-pdk_layermap 'pdkname tool src dst stackup <file>'",
              example=[
                  "cli: -pdk_layermap 'asap7 klayout db gds M10 asap7.map'",
                  "api: chip.set('pdk','asap7','layermap','klayout','db','gds','M10','asap7.map')"],
              schelp="""
              Files describing input/output mapping for streaming layout data from
              one format to another. A foundry PDK will include an official layer
              list for all user entered and generated layers supported in the GDS
              accepted by the foundry for processing, but there is no standardized
              layer definition format that can be read and written by all EDA tools.
              To ensure mask layer matching, key/value type mapping files are needed
              to convert EDA databases to/from GDS and to convert between different
              types of EDA databases. Layer maps are specified on a per metal
              stackup basis. The 'src' and 'dst' can be names of SC supported tools
              or file formats (like 'gds').""")
  
  
      scparam(cfg, ['pdk', pdkname, 'display', tool, stackup],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: display file",
              switch="-pdk_display 'pdkname tool stackup <file>'",
              example=[
                  "cli: -pdk_display 'asap7 klayout M10 display.lyt'",
                  "api: chip.set('pdk','asap7','display','klayout','M10','display.cfg')"],
              schelp="""
              Display configuration files describing colors and pattern schemes for
              all layers in the PDK. The display configuration file is entered on a
              stackup and tool basis.""")
  
      #TODO: create firm list of accepted files
      libarch = 'default'
      scparam(cfg, ['pdk', pdkname, 'aprtech', tool, stackup, libarch, filetype],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: APR technology files",
              switch="-pdk_aprtech 'pdkname tool stackup libarch filetype <file>'",
              example=[
                  "cli: -pdk_aprtech 'asap7 openroad M10 12t lef tech.lef'",
                  "api: chip.set('pdk','asap7','aprtech','openroad','M10','12t','lef','tech.lef')"],
              schelp="""
              Technology file containing setup information needed to enable DRC clean APR
              for the specified stackup, libarch, and format. The 'libarch' specifies the
              library architecture (e.g. library height). For example a PDK with support
              for 9 and 12 track libraries might have 'libarchs' called 9t and 12t.
              The standard filetype for specifying place and route design rules for a
              process node is through a 'lef' format technology file. The
              'filetype' used in the aprtech is used by the tool specific APR TCL scripts
              to set up the technology parameters. Some tools may require additional
              files beyond the tech.lef file. Examples of extra file types include
              antenna, tracks, tapcell, viarules, em.""")
  
      checks = ['lvs', 'drc', 'erc', 'fill']
      name = 'default'
      for item in checks:
          scparam(cfg, ['pdk', pdkname, item, 'runset', tool, stackup, name],
                  sctype='[file]',
                  scope='global',
                  shorthelp=f"PDK: {item.upper()} runset files",
                  switch=f"-pdk_{item}_runset 'pdkname tool stackup name <file>'",
                  example=[
                      f"cli: -pdk_{item}_runset 'asap7 magic M10 basic $PDK/{item}.rs'",
                      f"api: chip.set('pdk', 'asap7','{item}','runset','magic','M10','basic','$PDK/{item}.rs')"],
                  schelp=f"""Runset files for {item.upper()} task.""")
  
          scparam(cfg, ['pdk', pdkname, item, 'waiver', tool, stackup, name],
                  sctype='[file]',
                  scope='global',
                  shorthelp=f"PDK: {item.upper()} waiver files",
                  switch=f"-pdk_{item}_waiver 'tool stackup name <file>'",
                  example=[
                      f"cli: -pdk_{item}_waiver 'asap7 magic M10 basic $PDK/{item}.txt'",
                      f"api: chip.set('pdk', 'asap7','{item}','waiver','magic','M10','basic','$PDK/{item}.txt')"],
                  schelp=f"""Waiver files for {item.upper()} task.""")
  
      ################
      # Routing grid
      ################
  
      layer = 'default'
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'name'],
              sctype='str',
              scope='global',
              shorthelp="PDK: routing grid name map",
              switch="-pdk_grid_name 'pdkname stackup layer <str>'",
              example=[
                  "cli: -pdk_grid_name 'asap7 M10 metal1 m1'",
                  "api: chip.set('pdk','asap7','grid','M10','metal1','name','m1')"],
              schelp="""
              Maps PDK metal names to the SC standardized layer stack
              starting with m1 as the lowest routing layer and ending
              with m<n> as the highest routing layer. The map is
              specified on a per metal stack basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'dir'],
              sctype='str',
              scope='global',
              shorthelp="PDK: routing grid preferred direction",
              switch="-pdk_grid_dir 'pdkname stackup layer <str>'",
              example=[
                  "cli: -pdk_grid_dir 'asap7 M10 m1 horizontal'",
                  "api: chip.set('pdk','asap7','grid','M10','m1','dir','horizontal')"],
              schelp="""
              Preferred routing direction specified on a per stackup
              and per metal basis. Valid routing directions are horizontal
              and vertical.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'xpitch'],
              sctype='float',
              scope='global',
              unit='um',
              shorthelp="PDK: routing grid vertical wire pitch",
              switch="-pdk_grid_xpitch 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_xpitch 'asap7 M10 m1 0.5'",
                  "api: chip.set('pdk', 'asap7','grid','M10','m1','xpitch','0.5')"],
              schelp="""
              Defines the routing pitch for vertical wires on a per stackup and
              per metal basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'ypitch'],
              sctype='float',
              scope='global',
              unit='um',
              shorthelp="PDK: routing grid horizontal wire pitch",
              switch="-pdk_grid_ypitch 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_ypitch 'asap7 M10 m1 0.5'",
                  "api: chip.set('pdk','asap7','grid','M10','m1','ypitch','0.5')"],
              schelp="""
              Defines the routing pitch for horizontal wires on a per stackup and
              per metal basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'xoffset'],
              sctype='float',
              scope='global',
              unit='um',
              shorthelp="PDK: routing grid vertical wire offset",
              switch="-pdk_grid_xoffset 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_xoffset 'asap7 M10 m2 0.5'",
                  "api: chip.set('pdk','asap7','grid','M10','m2','xoffset','0.5')"],
              schelp="""
              Defines the grid offset of a vertical metal layer specified on a per
              stackup and per metal basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'yoffset'],
              sctype='float',
              scope='global',
              unit='um',
              shorthelp="PDK: routing grid horizontal wire offset",
              switch="-pdk_grid_yoffset 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_yoffset 'asap7 M10 m2 0.5'",
                  "api: chip.set('pdk', 'asap7','grid','M10','m2','yoffset','0.5')"],
              schelp="""
              Defines the grid offset of a horizontal metal layer specified on a per
              stackup and per metal basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'adj'],
              sctype='float',
              scope='global',
              shorthelp="PDK: routing grid resource adjustment",
              switch="-pdk_grid_adj 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_adj 'asap7 M10 m2 0.5'",
                  "api: chip.set('pdk','asap7','grid','M10','m2','adj','0.5')"],
              schelp="""
              Defines the routing resources adjustments for the design on a per layer
              basis. The value is expressed as a fraction from 0 to 1. A value of
              0.5 reduces the routing resources by 50%. If not defined, 100%
              routing resource utilization is permitted.""")
  
      corner='default'
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'cap', corner],
              sctype='float',
              scope='global',
              unit='ff/um',
              shorthelp="PDK: routing grid unit capacitance",
              switch="-pdk_grid_cap 'pdkname stackup layer corner <float>''",
              example= [
                  "cli: -pdk_grid_cap 'asap7 M10 m2 fast 0.2'",
                  "api: chip.set('pdk','asap7','grid','M10','m2','cap','fast','0.2')"],
              schelp="""
              Unit capacitance of a wire defined by the grid width and spacing values
              in the 'grid' structure specified on a per
              stackup, metal, and corner basis. As a rough rule of thumb, this value
              tends to stay around 0.2ff/um. This number should only be used for
              reality confirmation. Accurate analysis should use the PEX models.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'res', corner],
              sctype='float',
              scope='global',
              unit='ohm/um',
              shorthelp="PDK: routing grid unit resistance",
              switch="-pdk_grid_res 'pdkname stackup layer corner <float>''",
              example= [
                  "cli: -pdk_grid_res 'asap7 M10 m2 fast 0.2'",
                  "api: chip.set('pdk','asap7','grid','M10','m2','res','fast','0.2')"],
              schelp="""
              Resistance of a wire defined by the grid width and spacing values
              in the 'grid' structure specified as ohms/um on a per
              stackup, metal, and corner basis. The parameter is only meant to be
              used as a sanity check and for coarse design planning. Accurate
              analysis should use the TCAD PEX models.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'tcr', corner],
              sctype='float',
              scope='global',
              unit='%/degree(T)',
              shorthelp="PDK: routing grid temperature coefficient",
              switch="-pdk_grid_tcr 'pdkname stackup layer
  
  [... the rest is omitted ...]
files.new_path: siliconcompiler/schema.py
files.old_contents: # Copyright 2020 Silicon Compiler Authors. All Rights Reserved.
  
  from siliconcompiler import utils
  
  import re
  import os
  import sys
  import copy as pycopy
  import json
  
  SCHEMA_VERSION = '0.9.0'
  
  #############################################################################
  # PARAM DEFINITION
  #############################################################################
  
  def scparam(cfg,
              keypath,
              sctype=None,
              require=None,
              defvalue=None,
              scope='job',
              copy='false',
              lock='false',
              hashalgo='sha256',
              signature=None,
              notes=None,
              unit=None,
              shorthelp=None,
              switch=None,
              example=None,
              schelp=None):
  
      # 1. decend keypath until done
      # 2. create key if missing
      # 3. populate leaf cell when keypath empty
      if keypath:
          key = keypath[0]
          keypath.pop(0)
          if key not in cfg.keys():
              cfg[key] = {}
          scparam(cfg[key],
                  keypath,
                  sctype=sctype,
                  scope=scope,
                  require=require,
                  defvalue=defvalue,
                  copy=copy,
                  lock=lock,
                  hashalgo=hashalgo,
                  signature=signature,
                  notes=notes,
                  unit=unit,
                  shorthelp=shorthelp,
                  switch=switch,
                  example=example,
                  schelp=schelp)
      else:
  
          # removing leading spaces as if schelp were a docstring
          schelp = utils.trim(schelp)
  
          # setting valus based on types
          # note (bools are never lists)
          if re.match(r'bool',sctype):
              require = 'all'
              if defvalue is None:
                  defvalue = 'false'
          if re.match(r'\[',sctype) and signature is None:
              signature = []
          if re.match(r'\[',sctype) and defvalue is None:
              defvalue = []
  
          # mandatory for all
          cfg['defvalue'] = defvalue
          cfg['value'] = pycopy.copy(defvalue)
          cfg['type'] = sctype
          cfg['scope'] = scope
          cfg['require'] = require
          cfg['lock'] = lock
          cfg['switch'] = switch
          cfg['shorthelp'] = shorthelp
          cfg['example'] = example
          cfg['help'] = schelp
          cfg['signature'] = signature
          cfg['notes'] = notes
  
          # unit for floats/ints
          if unit is not None:
              cfg['unit'] = unit
  
          # file only values
          if re.search(r'file',sctype):
              cfg['hashalgo'] = hashalgo
              cfg['copy'] = copy
              cfg['filehash'] = []
              cfg['date'] = []
              cfg['author'] = []
  
  
  #############################################################################
  # CHIP CONFIGURATION
  #############################################################################
  
  def schema_cfg():
      '''Method for defining Chip configuration schema
      All the keys defined in this dictionary are reserved words.
      '''
  
      # SC version number (bump on every non trivial change)
      # Version number following semver standard.
      # Software version syncs with SC releases (from _metadata)
  
      SCHEMA_VERSION = '0.8.0'
  
      # Basic schema setup
      cfg = {}
  
      # Place holder dictionaries updated by core methods()
      cfg['history'] = {}
      cfg['library'] = {}
  
      scparam(cfg,['schemaversion'],
              sctype='str',
              scope='global',
              defvalue=SCHEMA_VERSION,
              require='all',
              shorthelp="Schema version number",
              lock='true',
              switch="-schemaversion <str>",
              example=["api: chip.get('schemaversion')"],
              schelp="""SiliconCompiler schema version number.""")
  
      # Design topmodule/entrypoint
      scparam(cfg,['design'],
              sctype='str',
              scope='global',
              require='all',
              shorthelp="Design top module name",
              switch="-design <str>",
              example=["cli: -design hello_world",
                      "api: chip.set('design', 'hello_world')"],
              schelp="""Name of the top level module or library. Required for all
              chip objects.""")
  
      # input/output
      io = {'input': ['Input','true'],
            'output': ['Output','false']
      }
  
      filetype = 'default'
      for item, val in io.items():
          scparam(cfg,[item, filetype],
                  sctype='[file]',
                  copy=f"{val[1]}",
                  shorthelp=f"{val[0]} files",
                  switch=f"-{item} 'filetype <file>'",
                  example=[
                      f"cli: -{item} 'verilog hello_world.v'",
                      f"api: chip.set({item},'verilog','hello_world.v')"],
                  schelp=f"""
                  List of {item} files specifed by type. The filetype name must
                  align with the parameter names within the flow and tool setup
                  scripts. Examples of acceptable file types include python, c,
                  systemc, verilog, vhdl, netlist, def, gds, gerber, saif, sdc,
                  saif, vcd, spef, sdf.""")
  
      # Inputs and constraints
      cfg = schema_constraint(cfg)
  
      # Options
      cfg = schema_option(cfg)
      cfg = schema_arg(cfg)
      cfg = schema_unit(cfg)
  
      # Technology configuration
      cfg = schema_fpga(cfg)
      cfg = schema_asic(cfg)
      cfg = schema_pdk(cfg)
  
      # Flows
      cfg = schema_tool(cfg)
      cfg = schema_flowgraph(cfg)
  
      # Metrics
      cfg = schema_checklist(cfg)
      cfg = schema_metric(cfg)
      cfg = schema_record(cfg)
  
      # Modeling
      cfg = schema_model(cfg)
  
      # Datasheeet
      cfg = schema_datasheet(cfg)
  
      # Packaging
      cfg = schema_package(cfg)
  
      return cfg
  
  ###############################################################################
  # FPGA
  ###############################################################################
  
  def schema_fpga(cfg):
      ''' FPGA configuration
      '''
  
      scparam(cfg,['fpga', 'arch'],
              sctype='[file]',
              copy='true',
              shorthelp="FPGA: architecture file",
              switch="-fpga_arch <file>",
              example=["cli: -fpga_arch myfpga.xml",
                       "api:  chip.set('fpga', 'arch', 'myfpga.xml')"],
              schelp=""" Architecture definition file for FPGA place and route
              tool. For the VPR tool, the file is a required XML based description,
              allowing targeting a large number of virtual and commercial
              architectures. For most commercial tools, the fpga part name provides
              enough information to enable compilation and the 'arch' parameter is
              optional.""")
  
      scparam(cfg,['fpga', 'vendor'],
              sctype='str',
              shorthelp="FPGA: vendor name",
              switch="-fpga_vendor <str>",
              example=["cli: -fpga_vendor acme",
                      "api:  chip.set('fpga', 'vendor', 'acme')"],
              schelp="""
              Name of the FPGA vendor. The parameter is used to check part
              name and to select the eda tool flow in case 'edaflow' is
              unspecified.""")
  
      scparam(cfg,['fpga', 'partname'],
              sctype='str',
              require='fpga',
              shorthelp="FPGA: part name",
              switch="-fpga_partname <str>",
              example=["cli: -fpga_partname fpga64k",
                       "api:  chip.set('fpga', 'partname', 'fpga64k')"],
              schelp="""
              Complete part name used as a device target by the FPGA compilation
              tool. The part name must be an exact string match to the partname
              hard coded within the FPGA eda tool.""")
  
      scparam(cfg,['fpga', 'board'],
              sctype='str',
              shorthelp="FPGA: board name",
              switch="-fpga_board <str>",
              example=["cli: -fpga_board parallella",
                       "api:  chip.set('fpga', 'board', 'parallella')"],
              schelp="""
              Complete board name used as a device target by the FPGA compilation
              tool. The board name must be an exact string match to the partname
              hard coded within the FPGA eda tool. The parameter is optional and can
              be used in place of a partname and pin constraints for some tools.""")
  
      scparam(cfg,['fpga', 'program'],
              sctype='bool',
              shorthelp="FPGA: program enable",
              switch="-fpga_program <bool>",
              example=["cli: -fpga_program",
                       "api:  chip.set('fpga', 'program', True)"],
              schelp="""Specifies that the bitstream should be loaded into an FPGA.""")
  
      scparam(cfg,['fpga', 'flash'],
              sctype='bool',
              shorthelp="FPGA: flash enable",
              switch="-fpga_flash <bool>",
              example=["cli: -fpga_flash",
                       "api:  chip.set('fpga', 'flash', True)"],
              schelp="""Specifies that the bitstream should be flashed in the board/device.
              The default is to load the bitstream into volatile memory (SRAM).""")
  
      return cfg
  
  ###############################################################################
  # PDK
  ###############################################################################
  
  def schema_pdk(cfg, stackup='default'):
      ''' Process design kit configuration
      '''
  
      tool = 'default'
      filetype = 'default'
      pdkname = 'default'
  
      scparam(cfg, ['pdk', pdkname, 'foundry'],
              sctype='str',
              scope='global',
              require="asic",
              shorthelp="PDK: foundry name",
              switch="-pdk_foundry 'pdkname <str>'",
              example=["cli: -pdk_foundry 'asap7 virtual'",
                      "api:  chip.set('pdk', 'asap7', 'foundry', 'virtual')"],
              schelp="""
              Name of foundry corporation. Examples include intel, gf, tsmc,
              samsung, skywater, virtual. The \'virtual\' keyword is reserved for
              simulated non-manufacturable processes.""")
  
      scparam(cfg, ['pdk', pdkname, 'node'],
              sctype='float',
              scope='global',
              require="asic",
              shorthelp="PDK: process node",
              switch="-pdk_node 'pdkname <float>'",
              example=["cli: -pdk_node 'asap7 130'",
                      "api:  chip.set('pdk', 'asap7', 'node', 130)"],
              schelp="""
              Approximate relative minimum dimension of the process target specified
              in nanometers. The parameter is required for flows and tools that
              leverage the value to drive technology dependent synthesis and APR
              optimization. Node examples include 180, 130, 90, 65, 45, 32, 22 14,
              10, 7, 5, 3.""")
  
      scparam(cfg, ['pdk', pdkname, 'version'],
              sctype='str',
              scope='global',
              shorthelp="PDK: version",
              switch="-pdk_version 'pdkname <str>'",
              example=["cli: -pdk_version 'asap7 1.0'",
                      "api:  chip.set('pdk', 'asap7', 'version', '1.0')"],
              schelp="""
              Alphanumeric string specifying the version of the PDK. Verification of
              correct PDK and IP versions is a hard ASIC tapeout require in all
              commercial foundries. The version number can be used for design manifest
              tracking and tapeout checklists.""")
  
      scparam(cfg, ['pdk', pdkname, 'stackup'],
              sctype='[str]',
              scope='global',
              require='asic',
              shorthelp="PDK: metal stackups",
              switch="-pdk_stackup 'pdkname <str>'",
              example=["cli: -pdk_stackup 'asap7 2MA4MB2MC'",
                       "api: chip.add('pdk', 'asap7','stackup','2MA4MB2MC')"],
              schelp="""
              List of all metal stackups offered in the process node. Older process
              nodes may only offer a single metal stackup, while advanced nodes
              offer a large but finite list of metal stacks with varying combinations
              of metal line pitches and thicknesses. Stackup naming is unique to a
              foundry, but is generally a long string or code. For example, a 10
              metal stackup with two 1x wide, four 2x wide, and 4x wide metals,
              might be identified as 2MA4MB2MC, where MA, MB, and MC denote wiring
              layers with different properties (thickness, width, space). Each
              stackup will come with its own set of routing technology files and
              parasitic models specified in the pdk_pexmodel and pdk_aprtech
              parameters.""")
  
      scparam(cfg, ['pdk', pdkname, 'thickness', stackup],
              sctype='float',
              scope='global',
              unit='mm',
              shorthelp="PDK: unit thickness",
              switch="-pdk_thickness 'pdkname stackup <float>'",
              example=["cli: -pdk_thickness 'asap7 2MA4MB2MC 1.57'",
                      "api:  chip.set('pdk', 'asap7', 'thickness', '2MA4MB2MC', 1.57)"],
              schelp="""
              Thickness of a manfuatured unit specified on a per stackup basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'wafersize'],
              sctype='float',
              scope='global',
              unit='mm',
              require="asic",
              shorthelp="PDK: wafer size",
              switch="-pdk_wafersize 'pdkname <float>'",
              example=["cli: -pdk_wafersize 'asap7 300'",
                      "api:  chip.set('pdk', 'asap7', 'wafersize', 300)"],
              schelp="""
              Wafer diameter used in wafer based manufacturing process. The standard diameter
              for leading edge manufacturing is 300mm. For older process technologies
              and specialty fabs, smaller diameters such as 200, 100, 125, 100 are common.
              The value is used to calculate dies per wafer and full factory chip costs.""")
  
      scparam(cfg, ['pdk', pdkname, 'panelsize'],
              sctype='[(float,float)]',
              scope='global',
              unit='mm',
              shorthelp="PDK: panel size",
              switch="-pdk_panelsize 'pdkname <float>'",
              example=["cli: -pdk_panelsize 'asap7 (45.72,60.96)'",
                      "api:  chip.set('pdk', 'asap7', 'panelsize', (45.72,60.96))"],
              schelp="""
              List of panel sizes supported in the manufacturing process.
              """)
  
      scparam(cfg, ['pdk', pdkname, 'unitcost'],
              sctype='float',
              scope='global',
              unit='USD',
              shorthelp="PDK: unit cost",
              switch="-pdk_unitcost 'pdkname <float>'",
              example=["cli: -pdk_unitcost 'asap7 10000'",
                       "api:  chip.set('pdk', 'asap7', 'unitcost', 10000)"],
              schelp="""
              Raw cost per unit shipped by the factory, not accounting for yield
              loss.""")
  
      scparam(cfg, ['pdk', pdkname, 'd0'],
              sctype='float',
              scope='global',
              shorthelp="PDK: process defect density",
              switch="-pdk_d0 'pdkname <float>'",
              example=["cli: -pdk_d0 'asap7 0.1'",
                       "api:  chip.set('pdk', 'asap7', 'd0', 0.1)"],
              schelp="""
              Process defect density (d0) expressed as random defects per cm^2. The
              value is used to calculate yield losses as a function of area, which in
              turn affects the chip full factory costs. Two yield models are
              supported: Poisson (default), and Murphy. The Poisson based yield is
              calculated as dy = exp(-area * d0/100). The Murphy based yield is
              calculated as dy = ((1-exp(-area * d0/100))/(area * d0/100))^2.""")
  
      scparam(cfg, ['pdk', pdkname, 'hscribe'],
              sctype='float',
              scope='global',
              unit='mm',
              shorthelp="PDK: horizontal scribe line width",
              switch="-pdk_hscribe 'pdkname <float>'",
              example=["cli: -pdk_hscribe 'asap7 0.1'",
                       "api:  chip.set('pdk', 'asap7', 'hscribe', 0.1)"],
              schelp="""
              Width of the horizontal scribe line used during die separation.
              The process is generally completed using a mechanical saw, but can be
              done through combinations of mechanical saws, lasers, wafer thinning,
              and chemical etching in more advanced technologies. The value is used
              to calculate effective dies per wafer and full factory cost.""")
  
      scparam(cfg, ['pdk', pdkname, 'vscribe'],
              sctype='float',
              scope='global',
              unit='mm',
              shorthelp="PDK: vertical scribe line width",
              switch="-pdk_vscribe 'pdkname <float>'",
              example=["cli: -pdk_vscribe 'asap7 0.1'",
                       "api:  chip.set('pdk', 'asap7', 'vscribe', 0.1)"],
              schelp="""
               Width of the vertical scribe line used during die separation.
              The process is generally completed using a mechanical saw, but can be
              done through combinations of mechanical saws, lasers, wafer thinning,
              and chemical etching in more advanced technologies. The value is used
              to calculate effective dies per wafer and full factory cost.""")
  
      scparam(cfg, ['pdk', pdkname, 'edgemargin'],
              sctype='float',
              scope='global',
              unit='mm',
              shorthelp="PDK: wafer edge keep-out margin",
              switch="-pdk_edgemargin 'pdkname <float>'",
              example=[
                  "cli: -pdk_edgemargin 'asap7 1'",
                  "api:  chip.set('pdk', 'asap7', 'edgemargin', 1)"],
              schelp="""
              Keep-out distance/margin from the edge inwards. The edge
              is prone to chipping and need special treatment that preclude
              placement of designs in this area. The edge value is used to
              calculate effective units per wafer/panel and full factory cost.""")
  
      scparam(cfg, ['pdk', pdkname, 'density'],
              sctype='float',
              scope='global',
              shorthelp="PDK: transistor density",
              switch="-pdk_density 'pdkname <float>'",
              example=["cli: -pdk_density 'asap7 100e6'",
                      "api:  chip.set('pdk', 'asap7', 'density', 10e6)"],
              schelp="""
              Approximate logic density expressed as # transistors / mm^2
              calculated as:
              0.6 * (Nand2 Transistor Count) / (Nand2 Cell Area) +
              0.4 * (Register Transistor Count) / (Register Cell Area)
              The value is specified for a fixed standard cell library within a node
              and will differ depending on the library vendor, library track height
              and library type. The value can be used to to normalize the effective
              density reported for the design across different process nodes. The
              value can be derived from a variety of sources, including the PDK DRM,
              library LEFs, conference presentations, and public analysis.""")
  
      simtype = 'default'
      scparam(cfg, ['pdk', pdkname, 'devmodel', tool, simtype, stackup],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: device models",
              switch="-pdk_devmodel 'pdkname tool simtype stackup <file>'",
              example=[
              "cli: -pdk_devmodel 'asap7 xyce spice M10 asap7.sp'",
              "api: chip.set('pdk','asap7','devmodel','xyce','spice','M10','asap7.sp')"],
              schelp="""
              List of filepaths to PDK device models for different simulation
              purposes and for different tools. Examples of device model types
              include spice, aging, electromigration, radiation. An example of a
              'spice' tool is xyce. Device models are specified on a per metal stack
              basis. Process nodes with a single device model across all stacks will
              have a unique parameter record per metal stack pointing to the same
              device model file.  Device types and tools are dynamic entries
              that depend on the tool setup and device technology. Pseud-standardized
              device types include spice, em (electromigration), and aging.""")
  
      corner='default'
      scparam(cfg, ['pdk', pdkname, 'pexmodel', tool, stackup, corner],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: parasitic TCAD models",
              switch="-pdk_pexmodel 'pdkname tool stackup corner <file>'",
              example=[
                  "cli: -pdk_pexmodel 'asap7 fastcap M10 max wire.mod'",
                  "api: chip.set('pdk','asap7','pexmodel','fastcap','M10','max','wire.mod')"],
              schelp="""
              List of filepaths to PDK wire TCAD models used during automated
              synthesis, APR, and signoff verification. Pexmodels are specified on
              a per metal stack basis. Corner values depend on the process being
              used, but typically include nomenclature such as min, max, nominal.
              For exact names, refer to the DRM. Pexmodels are generally not
              standardized and specified on a per tool basis. An example of pexmodel
              type is 'fastcap'.""")
  
      src = 'default'
      dst = 'default'
      scparam(cfg, ['pdk', pdkname, 'layermap', tool, src, dst, stackup],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: layer map file",
              switch="-pdk_layermap 'pdkname tool src dst stackup <file>'",
              example=[
                  "cli: -pdk_layermap 'asap7 klayout db gds M10 asap7.map'",
                  "api: chip.set('pdk','asap7','layermap','klayout','db','gds','M10','asap7.map')"],
              schelp="""
              Files describing input/output mapping for streaming layout data from
              one format to another. A foundry PDK will include an official layer
              list for all user entered and generated layers supported in the GDS
              accepted by the foundry for processing, but there is no standardized
              layer definition format that can be read and written by all EDA tools.
              To ensure mask layer matching, key/value type mapping files are needed
              to convert EDA databases to/from GDS and to convert between different
              types of EDA databases. Layer maps are specified on a per metal
              stackup basis. The 'src' and 'dst' can be names of SC supported tools
              or file formats (like 'gds').""")
  
  
      scparam(cfg, ['pdk', pdkname, 'display', tool, stackup],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: display file",
              switch="-pdk_display 'pdkname tool stackup <file>'",
              example=[
                  "cli: -pdk_display 'asap7 klayout M10 display.lyt'",
                  "api: chip.set('pdk','asap7','display','klayout','M10','display.cfg')"],
              schelp="""
              Display configuration files describing colors and pattern schemes for
              all layers in the PDK. The display configuration file is entered on a
              stackup and tool basis.""")
  
      #TODO: create firm list of accepted files
      libarch = 'default'
      scparam(cfg, ['pdk', pdkname, 'aprtech', tool, stackup, libarch, filetype],
              sctype='[file]',
              scope='global',
              shorthelp="PDK: APR technology files",
              switch="-pdk_aprtech 'pdkname tool stackup libarch filetype <file>'",
              example=[
                  "cli: -pdk_aprtech 'asap7 openroad M10 12t lef tech.lef'",
                  "api: chip.set('pdk','asap7','aprtech','openroad','M10','12t','lef','tech.lef')"],
              schelp="""
              Technology file containing setup information needed to enable DRC clean APR
              for the specified stackup, libarch, and format. The 'libarch' specifies the
              library architecture (e.g. library height). For example a PDK with support
              for 9 and 12 track libraries might have 'libarchs' called 9t and 12t.
              The standard filetype for specifying place and route design rules for a
              process node is through a 'lef' format technology file. The
              'filetype' used in the aprtech is used by the tool specific APR TCL scripts
              to set up the technology parameters. Some tools may require additional
              files beyond the tech.lef file. Examples of extra file types include
              antenna, tracks, tapcell, viarules, em.""")
  
      checks = ['lvs', 'drc', 'erc', 'fill']
      name = 'default'
      for item in checks:
          scparam(cfg, ['pdk', pdkname, item, 'runset', tool, stackup, name],
                  sctype='[file]',
                  scope='global',
                  shorthelp=f"PDK: {item.upper()} runset files",
                  switch=f"-pdk_{item}_runset 'pdkname tool stackup name <file>'",
                  example=[
                      f"cli: -pdk_{item}_runset 'asap7 magic M10 basic $PDK/{item}.rs'",
                      f"api: chip.set('pdk', 'asap7','{item}','runset','magic','M10','basic','$PDK/{item}.rs')"],
                  schelp=f"""Runset files for {item.upper()} task.""")
  
          scparam(cfg, ['pdk', pdkname, item, 'waiver', tool, stackup, name],
                  sctype='[file]',
                  scope='global',
                  shorthelp=f"PDK: {item.upper()} waiver files",
                  switch=f"-pdk_{item}_waiver 'tool stackup name <file>'",
                  example=[
                      f"cli: -pdk_{item}_waiver 'asap7 magic M10 basic $PDK/{item}.txt'",
                      f"api: chip.set('pdk', 'asap7','{item}','waiver','magic','M10','basic','$PDK/{item}.txt')"],
                  schelp=f"""Waiver files for {item.upper()} task.""")
  
      ################
      # Routing grid
      ################
  
      layer = 'default'
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'name'],
              sctype='str',
              scope='global',
              shorthelp="PDK: routing grid name map",
              switch="-pdk_grid_name 'pdkname stackup layer <str>'",
              example=[
                  "cli: -pdk_grid_name 'asap7 M10 metal1 m1'",
                  "api: chip.set('pdk','asap7','grid','M10','metal1','name','m1')"],
              schelp="""
              Maps PDK metal names to the SC standardized layer stack
              starting with m1 as the lowest routing layer and ending
              with m<n> as the highest routing layer. The map is
              specified on a per metal stack basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'dir'],
              sctype='str',
              scope='global',
              shorthelp="PDK: routing grid preferred direction",
              switch="-pdk_grid_dir 'pdkname stackup layer <str>'",
              example=[
                  "cli: -pdk_grid_dir 'asap7 M10 m1 horizontal'",
                  "api: chip.set('pdk','asap7','grid','M10','m1','dir','horizontal')"],
              schelp="""
              Preferred routing direction specified on a per stackup
              and per metal basis. Valid routing directions are horizontal
              and vertical.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'xpitch'],
              sctype='float',
              scope='global',
              unit='um',
              shorthelp="PDK: routing grid vertical wire pitch",
              switch="-pdk_grid_xpitch 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_xpitch 'asap7 M10 m1 0.5'",
                  "api: chip.set('pdk', 'asap7','grid','M10','m1','xpitch','0.5')"],
              schelp="""
              Defines the routing pitch for vertical wires on a per stackup and
              per metal basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'ypitch'],
              sctype='float',
              scope='global',
              unit='um',
              shorthelp="PDK: routing grid horizontal wire pitch",
              switch="-pdk_grid_ypitch 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_ypitch 'asap7 M10 m1 0.5'",
                  "api: chip.set('pdk','asap7','grid','M10','m1','ypitch','0.5')"],
              schelp="""
              Defines the routing pitch for horizontal wires on a per stackup and
              per metal basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'xoffset'],
              sctype='float',
              scope='global',
              unit='um',
              shorthelp="PDK: routing grid vertical wire offset",
              switch="-pdk_grid_xoffset 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_xoffset 'asap7 M10 m2 0.5'",
                  "api: chip.set('pdk','asap7','grid','M10','m2','xoffset','0.5')"],
              schelp="""
              Defines the grid offset of a vertical metal layer specified on a per
              stackup and per metal basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'yoffset'],
              sctype='float',
              scope='global',
              unit='um',
              shorthelp="PDK: routing grid horizontal wire offset",
              switch="-pdk_grid_yoffset 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_yoffset 'asap7 M10 m2 0.5'",
                  "api: chip.set('pdk', 'asap7','grid','M10','m2','yoffset','0.5')"],
              schelp="""
              Defines the grid offset of a horizontal metal layer specified on a per
              stackup and per metal basis.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'adj'],
              sctype='float',
              scope='global',
              shorthelp="PDK: routing grid resource adjustment",
              switch="-pdk_grid_adj 'pdkname stackup layer <float>'",
              example= [
                  "cli: -pdk_grid_adj 'asap7 M10 m2 0.5'",
                  "api: chip.set('pdk','asap7','grid','M10','m2','adj','0.5')"],
              schelp="""
              Defines the routing resources adjustments for the design on a per layer
              basis. The value is expressed as a fraction from 0 to 1. A value of
              0.5 reduces the routing resources by 50%. If not defined, 100%
              routing resource utilization is permitted.""")
  
      corner='default'
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'cap', corner],
              sctype='float',
              scope='global',
              unit='ff/um',
              shorthelp="PDK: routing grid unit capacitance",
              switch="-pdk_grid_cap 'pdkname stackup layer corner <float>''",
              example= [
                  "cli: -pdk_grid_cap 'asap7 M10 m2 fast 0.2'",
                  "api: chip.set('pdk','asap7','grid','M10','m2','cap','fast','0.2')"],
              schelp="""
              Unit capacitance of a wire defined by the grid width and spacing values
              in the 'grid' structure specified on a per
              stackup, metal, and corner basis. As a rough rule of thumb, this value
              tends to stay around 0.2ff/um. This number should only be used for
              reality confirmation. Accurate analysis should use the PEX models.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'res', corner],
              sctype='float',
              scope='global',
              unit='ohm/um',
              shorthelp="PDK: routing grid unit resistance",
              switch="-pdk_grid_res 'pdkname stackup layer corner <float>''",
              example= [
                  "cli: -pdk_grid_res 'asap7 M10 m2 fast 0.2'",
                  "api: chip.set('pdk','asap7','grid','M10','m2','res','fast','0.2')"],
              schelp="""
              Resistance of a wire defined by the grid width and spacing values
              in the 'grid' structure specified as ohms/um on a per
              stackup, metal, and corner basis. The parameter is only meant to be
              used as a sanity check and for coarse design planning. Accurate
              analysis should use the TCAD PEX models.""")
  
      scparam(cfg, ['pdk', pdkname, 'grid', stackup, layer, 'tcr', corner],
              sctype='float',
              scope='global',
              unit='%/degree(T)',
              shorthelp="PDK: routing grid temperature coefficient",
              switch="-pdk_grid_tcr 'pdkname stackup layer
  
  [... the rest is omitted ...]
files.old_path: siliconcompiler/schema.py
files.new_contents: {
      "arg": {
          "flow": {
              "default": {
                  "defvalue": [],
                  "example": [
                      "cli: -arg_flow 'n 100'",
                      "api: chip.set('arg','flow','n', 100)"
                  ],
                  "help": "Parameter passed in as key/value pair to the flow target\nreferenced in the load_flow() API call. See the target flow\nfor specific guidelines regarding configuration parameters.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "scratch",
                  "shorthelp": "ARG: Flow argument",
                  "signature": [],
                  "switch": "-arg_flow 'key <str>'",
                  "type": "[str]",
                  "value": []
              }
          },
          "index": {
              "defvalue": null,
              "example": [
                  "cli: -arg_index 0",
                  "api: chip.set('arg','index','0')"
              ],
              "help": "Dynamic parameter passed in by the sc runtime as an argument to\na runtime task. The parameter enables configuration code\n(usually TCL) to use control flow that depend on the current\n'index'. The parameter is used the run() function and\nis not intended for external use.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "scratch",
              "shorthelp": "ARG: Index argument",
              "signature": null,
              "switch": "-arg_index <str>",
              "type": "str",
              "value": null
          },
          "pdk": {
              "default": {
                  "defvalue": [],
                  "example": [
                      "cli: -arg_pdk 'mimcap true'",
                      "api: chip.set('arg','pdk','mimcap','true')"
                  ],
                  "help": "Parameter passed in as key/value pair to the technology target\nreferenced in the load_pdk() API call. See the target technology\nfor specific guidelines regarding configuration parameters.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "scratch",
                  "shorthelp": "ARG: PDK argument",
                  "signature": [],
                  "switch": "-arg_pdk 'key <str>",
                  "type": "[str]",
                  "value": []
              }
          },
          "step": {
              "defvalue": null,
              "example": [
                  "cli: -arg_step 'route'",
                  "api: chip.set('arg', 'step', 'route')"
              ],
              "help": "Dynamic parameter passed in by the sc runtime as an argument to\na runtime task. The parameter enables configuration code\n(usually TCL) to use control flow that depend on the current\n'step'. The parameter is used the run() function and\nis not intended for external use.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "scratch",
              "shorthelp": "ARG: Step argument",
              "signature": null,
              "switch": "-arg_step <str>",
              "type": "str",
              "value": null
          }
      },
      "asic": {
          "aspectratio": {
              "defvalue": null,
              "example": [
                  "cli: -asic_aspectratio 2.0",
                  "api: chip.set('asic', 'aspectratio', '2.0')"
              ],
              "help": "Height to width ratio of the block for automated floor-planning.\nValues below 0.1 and above 10 should be avoided as they will likely fail\nto converge during placement and routing. The ideal aspect ratio for\nmost designs is 1. This value is only used when no diearea or floorplan\nis supplied.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: block aspect ratio",
              "signature": null,
              "switch": "-asic_aspectratio <float>",
              "type": "float",
              "value": null
          },
          "cells": {
              "antenna": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_antenna '*eco*'",
                      "api: chip.set('asic','cells',antenna,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: antenna cell list",
                  "signature": [],
                  "switch": "-asic_cells_antenna '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "buf": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_buf '*eco*'",
                      "api: chip.set('asic','cells',buf,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: buf cell list",
                  "signature": [],
                  "switch": "-asic_cells_buf '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkbuf": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkbuf '*eco*'",
                      "api: chip.set('asic','cells',clkbuf,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkbuf cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkbuf '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkdelay": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkdelay '*eco*'",
                      "api: chip.set('asic','cells',clkdelay,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkdelay cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkdelay '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkgate": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkgate '*eco*'",
                      "api: chip.set('asic','cells',clkgate,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkgate cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkgate '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkicg": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkicg '*eco*'",
                      "api: chip.set('asic','cells',clkicg,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkicg cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkicg '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkinv": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkinv '*eco*'",
                      "api: chip.set('asic','cells',clkinv,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkinv cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkinv '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clklogic": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clklogic '*eco*'",
                      "api: chip.set('asic','cells',clklogic,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clklogic cell list",
                  "signature": [],
                  "switch": "-asic_cells_clklogic '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "decap": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_decap '*eco*'",
                      "api: chip.set('asic','cells',decap,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: decap cell list",
                  "signature": [],
                  "switch": "-asic_cells_decap '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "delay": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_delay '*eco*'",
                      "api: chip.set('asic','cells',delay,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: delay cell list",
                  "signature": [],
                  "switch": "-asic_cells_delay '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "driver": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_driver '*eco*'",
                      "api: chip.set('asic','cells',driver,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: driver cell list",
                  "signature": [],
                  "switch": "-asic_cells_driver '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "endcap": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_endcap '*eco*'",
                      "api: chip.set('asic','cells',endcap,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: endcap cell list",
                  "signature": [],
                  "switch": "-asic_cells_endcap '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "filler": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_filler '*eco*'",
                      "api: chip.set('asic','cells',filler,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: filler cell list",
                  "signature": [],
                  "switch": "-asic_cells_filler '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "hold": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_hold '*eco*'",
                      "api: chip.set('asic','cells',hold,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: hold cell list",
                  "signature": [],
                  "switch": "-asic_cells_hold '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "ignore": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_ignore '*eco*'",
                      "api: chip.set('asic','cells',ignore,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: ignore cell list",
                  "signature": [],
                  "switch": "-asic_cells_ignore '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "load": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_load '*eco*'",
                      "api: chip.set('asic','cells',load,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: load cell list",
                  "signature": [],
                  "switch": "-asic_cells_load '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "tap": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_tap '*eco*'",
                      "api: chip.set('asic','cells',tap,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: tap cell list",
                  "signature": [],
                  "switch": "-asic_cells_tap '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "tie": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_tie '*eco*'",
                      "api: chip.set('asic','cells',tie,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: tie cell list",
                  "signature": [],
                  "switch": "-asic_cells_tie '<str>'",
                  "type": "[str]",
                  "value": []
              }
          },
          "corearea": {
              "defvalue": [],
              "example": [
                  "cli: -asic_corearea '(0,0)'",
                  "api: chip.set('asic', 'corearea', (0,0))"
              ],
              "help": "List of (x,y) points that define the outline of the core area for the\nphysical design. Simple rectangle areas can be defined with two points,\none for the lower left corner and one for the upper right corner. All\nvalues are specified in microns.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: core area outline",
              "signature": [],
              "switch": "-asic_corearea <[(float,float)]>",
              "type": "[(float,float)]",
              "unit": "um",
              "value": []
          },
          "coremargin": {
              "defvalue": null,
              "example": [
                  "cli: -asic_coremargin 1",
                  "api: chip.set('asic', 'coremargin', '1')"
              ],
              "help": "Halo/margin between the die boundary and core placement for\nautomated floorplanning when no diearea or floorplan is\nsupplied.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: block core margin",
              "signature": null,
              "switch": "-asic_coremargin <float>",
              "type": "float",
              "unit": "um",
              "value": null
          },
          "delaymodel": {
              "defvalue": null,
              "example": [
                  "cli: -asic_delaymodel ccs",
                  "api: chip.set('asic', 'delaymodel', 'ccs')"
              ],
              "help": "Delay model to use for the target libs. Supported values\nare nldm and ccs.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: delay model",
              "signature": null,
              "switch": "-asic_delaymodel <str>",
              "type": "str",
              "value": null
          },
          "density": {
              "defvalue": null,
              "example": [
                  "cli: -asic_density 30",
                  "api: chip.set('asic', 'density', '30')"
              ],
              "help": "Target density based on the total design cell area reported\nafter synthesis. This number is used when no diearea or floorplan is\nsupplied. Any number between 1 and 100 is legal, but values above 50\nmay fail due to area/congestion issues during apr.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: target core density",
              "signature": null,
              "switch": "-asic_density <float>",
              "type": "float",
              "value": null
          },
          "diearea": {
              "defvalue": [],
              "example": [
                  "cli: -asic_diearea '(0,0)'",
                  "api: chip.set('asic', 'diearea', (0,0))"
              ],
              "help": "List of (x,y) points that define the outline of the die area for the\nphysical design. Simple rectangle areas can be defined with two points,\none for the lower left corner and one for the upper right corner. All\nvalues are specified in microns.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: die area outline",
              "signature": [],
              "switch": "-asic_diearea <[(float,float)]>",
              "type": "[(float,float)]",
              "unit": "um",
              "value": []
          },
          "dir": {
              "default": {
                  "default": {
                      "defvalue": [],
                      "example": [
                          "cli: -asic_dir 'lib atool db ~/libdb'",
                          "api: chip.set('asic','dir','atool','db','~/libdb')"
                      ],
                      "help": "List of named dirs specified on a per tool basis. The parameter\nshould only be used for specifying dirs that are not directly\nsupported by the ASIC schema.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: special directory",
                      "signature": [],
                      "switch": "-asic_dir 'tool key <dir>'",
                      "type": "[dir]",
                      "value": []
                  }
              }
          },
          "file": {
              "default": {
                  "default": {
                      "author": [],
                      "copy": "false",
                      "date": [],
                      "defvalue": [],
                      "example": [
                          "cli: -asic_file 'yosys presyn ~/presyn.tcl'",
                          "api: chip.set('asic','file','yosys','presyn','~/presyn.tcl')"
                      ],
                      "filehash": [],
                      "hashalgo": "sha256",
                      "help": "List of named files specified on a per tool basis.\nThe parameter should only be used for specifying files that are\nnot directly supported by the ASIC schema.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: special file",
                      "signature": [],
                      "switch": "-asic_file 'tool key<file>'",
                      "type": "[file]",
                      "value": []
                  }
              }
          },
          "footprint": {
              "default": {
                  "alias": {
                      "defvalue": [],
                      "example": [
                          "cli: -asic_footprint_alias '12track FreeCell'",
                          "api: chip.set('asic','footprint','12track','alias','FreeCell')"
                      ],
                      "help": "Alias for the footprint key that is sometimes needed when the footprint can\nbe referenced by multiple names. The key is the 'official' footprint.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: Footprint name aliases",
                      "signature": [],
                      "switch": "-asic_footprint_alias 'key <str>'",
                      "type": "[str]",
                      "value": []
                  },
                  "size": {
                      "defvalue": null,
                      "example": [
                          "cli: -asic_footprint_size 'core (1.0,1.0)'",
                          "api: chip.set('asic','footprint','core','size',(1.0,1.0))"
                      ],
                      "help": "Size of the footprint described as a (width, height) tuple in\nmicrons.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: Footprint size",
                      "signature": null,
                      "switch": "-asic_footprint_size 'key <str>'",
                      "type": "(float,float)",
                      "value": null
                  },
                  "symmetry": {
                      "defvalue": null,
                      "example": [
                          "cli: -asic_footprint_symmetry 'core X Y'",
                          "api: chip.set('asic','footprint','core','symmetry','X Y')"
                      ],
                      "help": "Footprint symmetry based on LEF standard definition. 'X' implies\nsymmetric about the x axis, 'Y' implies symmetry about the y axis, and\n'X Y' implies symmetry about the x and y axis.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: Footprint symmetry",
                      "signature": null,
                      "switch": "-asic_footprint_symmetry 'key <str>'",
                      "type": "str",
                      "value": null
                  }
              }
          },
          "hpinlayer": {
              "defvalue": null,
              "example": [
                  "cli: -asic_hpinlayer m4",
                  "api: chip.set('asic', 'hpinlayer', 'm4')"
              ],
              "help": "Metal layer to use for automated horizontal pin placement\nduring APR.  The metal layers can be specified as technology\nagnostic SC layers starting with m1 or as hard PDK specific\nlayer names.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: vertical pin layer",
              "signature": null,
              "switch": "-asic_hpinlayer <str>",
              "type": "str",
              "value": null
          },
          "libarch": {
              "defvalue": null,
              "example": [
                  "cli: -asic_libarch '12track'",
                  "api: chip.set('asic','libarch','12track')"
              ],
              "help": "The library architecture (e.g. library height) used to build the\ndesign. For example a PDK with support for 9 and 12 track libraries\nmight have 'libarchs' called 9t and 12t.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: library architecture",
              "signature": null,
              "switch": "-asic_libarch '<str>'",
              "type": "str",
              "value": null
          },
          "logiclib": {
              "defvalue": [],
              "example": [
                  "cli: -asic_logiclib nangate45",
                  "api: chip.set('asic', 'logiclib','nangate45')"
              ],
              "help": "List of all selected logic libraries libraries\nto use for optimization for a given library architecture\n(9T, 11T, etc).",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: logic libraries",
              "signature": [],
              "switch": "-asic_logiclib <str>",
              "type": "[str]",
              "value": []
          },
          "macrolib": {
              "defvalue": [],
              "example": [
                  "cli: -asic_macrolib sram64x1024",
                  "api: chip.set('asic', 'macrolib','sram64x1024')"
              ],
              "help": "List of macro libraries to be linked in during synthesis and place\nand route. Macro libraries are used for resolving instances but are\nnot used as targets for logic synthesis.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: macro libraries",
              "signature": [],
              "switch": "-asic_macrolib <str>",
              "type": "[str]",
              "value": []
          },
          "maxcap": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxcap '0.25e-12'",
                  "api: chip.set('asic', 'maxcap', '0.25e-12')"
              ],
              "help": "Maximum allowed capacitance per net.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum net capacitance",
              "signature": null,
              "switch": "-asic_maxcap <float>",
              "type": "float",
              "unit": "F",
              "value": null
          },
          "maxfanout": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxfanout 64",
                  "api: chip.set('asic', 'maxfanout', '64')"
              ],
              "help": " Maximum driver fanout allowed during automated place and route.\nThe parameter directs the APR tool to break up any net with fanout\nlarger than maxfanout into sub nets and buffer.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum fanout",
              "signature": null,
              "switch": "-asic_maxfanout <int>",
              "type": "int",
              "value": null
          },
          "maxlayer": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxlayer m2",
                  "api: chip.set('asic', 'maxlayer', 'm2')"
              ],
              "help": "Maximum SC metal layer name to be used for automated place and route .\nAlternatively the layer can be a string that matches a layer hard coded\nin the pdk_aprtech file. Designers wishing to use the same setup across\nmultiple process nodes should use the integer approach. For processes\nwith ambiguous starting routing layers, exact strings should be used.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum routing layer",
              "signature": null,
              "switch": "-asic_maxlayer <str>",
              "type": "str",
              "value": null
          },
          "maxlength": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxlength 1000",
                  "api: chip.set('asic', 'maxlength', '1000')"
              ],
              "help": "Maximum total wire length allowed in design during APR. Any\nnet that is longer than maxlength is broken up into segments by\nthe tool.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum wire length",
              "signature": null,
              "switch": "-asic_maxlength <float>",
              "type": "float",
              "unit": "um",
              "value": null
          },
          "maxslew": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxslew '0.25e-9'",
                  "api: chip.set('asic', 'maxslew', '0.25e-9')"
              ],
              "help": "Maximum allowed transition time per net.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum slew",
              "signature": null,
              "switch": "-asic_maxslew <float>",
              "type": "float",
              "unit": "s",
              "value": null
          },
          "minlayer": {
              "defvalue": null,
              "example": [
                  "cli: -asic_minlayer m2",
                  "api: chip.set('asic', 'minlayer', 'm2')"
              ],
              "help": "Minimum SC metal layer name to be used for automated place and route .\nAlternatively the layer can be a string that matches a layer hard coded\nin the pdk_aprtech file. Designers wishing to use the same setup across\nmultiple process nodes should use the integer approach. For processes\nwith ambiguous starting routing layers, exact strings should be used.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: minimum routing layer",
              "signature": null,
              "switch": "-asic_minlayer <str>",
              "type": "str",
              "value": null
          },
          "ndr": {
              "default": {
                  "defvalue": null,
                  "example": [
                      "cli: -asic_ndr 'clk (0.2,0.2)'",
                      "api: chip.set('asic','ndr','clk', (0.2,0.2))"
                  ],
                  "help": "Definitions of non-default routing rule specified on a per\nnet basis. Constraints are entered as a (width,space) tuples\nspecified in microns.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: non-default routing rule",
                  "signature": null,
                  "switch": "-asic_ndr 'netname <(float,float)>",
                  "type": "(float,float)",
                  "value": null
              }
          },
          "pdk": {
              "defvalue": null,
              "example": [
                  "cli: -asic_pdk freepdk45",
                  "api: chip.set('asic','pdk','freepdk45')"
              ],
              "help": "Target ASIC PDK to use in the design.",
              "lock": "false",
              "notes": null,
              "require": null,
             
  
  [... the rest is omitted ...]
files.new_path: tests/core/data/defaults.json
files.old_contents: {
      "arg": {
          "flow": {
              "default": {
                  "defvalue": [],
                  "example": [
                      "cli: -arg_flow 'n 100'",
                      "api: chip.set('arg','flow','n', 100)"
                  ],
                  "help": "Parameter passed in as key/value pair to the flow target\nreferenced in the load_flow() API call. See the target flow\nfor specific guidelines regarding configuration parameters.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "scratch",
                  "shorthelp": "ARG: Flow argument",
                  "signature": [],
                  "switch": "-arg_flow 'key <str>'",
                  "type": "[str]",
                  "value": []
              }
          },
          "index": {
              "defvalue": null,
              "example": [
                  "cli: -arg_index 0",
                  "api: chip.set('arg','index','0')"
              ],
              "help": "Dynamic parameter passed in by the sc runtime as an argument to\na runtime task. The parameter enables configuration code\n(usually TCL) to use control flow that depend on the current\n'index'. The parameter is used the run() function and\nis not intended for external use.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "scratch",
              "shorthelp": "ARG: Index argument",
              "signature": null,
              "switch": "-arg_index <str>",
              "type": "str",
              "value": null
          },
          "pdk": {
              "default": {
                  "defvalue": [],
                  "example": [
                      "cli: -arg_pdk 'mimcap true'",
                      "api: chip.set('arg','pdk','mimcap','true')"
                  ],
                  "help": "Parameter passed in as key/value pair to the technology target\nreferenced in the load_pdk() API call. See the target technology\nfor specific guidelines regarding configuration parameters.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "scratch",
                  "shorthelp": "ARG: PDK argument",
                  "signature": [],
                  "switch": "-arg_pdk 'key <str>",
                  "type": "[str]",
                  "value": []
              }
          },
          "step": {
              "defvalue": null,
              "example": [
                  "cli: -arg_step 'route'",
                  "api: chip.set('arg', 'step', 'route')"
              ],
              "help": "Dynamic parameter passed in by the sc runtime as an argument to\na runtime task. The parameter enables configuration code\n(usually TCL) to use control flow that depend on the current\n'step'. The parameter is used the run() function and\nis not intended for external use.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "scratch",
              "shorthelp": "ARG: Step argument",
              "signature": null,
              "switch": "-arg_step <str>",
              "type": "str",
              "value": null
          }
      },
      "asic": {
          "aspectratio": {
              "defvalue": null,
              "example": [
                  "cli: -asic_aspectratio 2.0",
                  "api: chip.set('asic', 'aspectratio', '2.0')"
              ],
              "help": "Height to width ratio of the block for automated floor-planning.\nValues below 0.1 and above 10 should be avoided as they will likely fail\nto converge during placement and routing. The ideal aspect ratio for\nmost designs is 1. This value is only used when no diearea or floorplan\nis supplied.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: block aspect ratio",
              "signature": null,
              "switch": "-asic_aspectratio <float>",
              "type": "float",
              "value": null
          },
          "cells": {
              "antenna": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_antenna '*eco*'",
                      "api: chip.set('asic','cells',antenna,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: antenna cell list",
                  "signature": [],
                  "switch": "-asic_cells_antenna '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "buf": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_buf '*eco*'",
                      "api: chip.set('asic','cells',buf,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: buf cell list",
                  "signature": [],
                  "switch": "-asic_cells_buf '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkbuf": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkbuf '*eco*'",
                      "api: chip.set('asic','cells',clkbuf,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkbuf cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkbuf '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkdelay": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkdelay '*eco*'",
                      "api: chip.set('asic','cells',clkdelay,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkdelay cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkdelay '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkgate": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkgate '*eco*'",
                      "api: chip.set('asic','cells',clkgate,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkgate cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkgate '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkicg": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkicg '*eco*'",
                      "api: chip.set('asic','cells',clkicg,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkicg cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkicg '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clkinv": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clkinv '*eco*'",
                      "api: chip.set('asic','cells',clkinv,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clkinv cell list",
                  "signature": [],
                  "switch": "-asic_cells_clkinv '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "clklogic": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_clklogic '*eco*'",
                      "api: chip.set('asic','cells',clklogic,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: clklogic cell list",
                  "signature": [],
                  "switch": "-asic_cells_clklogic '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "decap": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_decap '*eco*'",
                      "api: chip.set('asic','cells',decap,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: decap cell list",
                  "signature": [],
                  "switch": "-asic_cells_decap '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "delay": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_delay '*eco*'",
                      "api: chip.set('asic','cells',delay,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: delay cell list",
                  "signature": [],
                  "switch": "-asic_cells_delay '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "driver": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_driver '*eco*'",
                      "api: chip.set('asic','cells',driver,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: driver cell list",
                  "signature": [],
                  "switch": "-asic_cells_driver '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "endcap": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_endcap '*eco*'",
                      "api: chip.set('asic','cells',endcap,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: endcap cell list",
                  "signature": [],
                  "switch": "-asic_cells_endcap '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "filler": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_filler '*eco*'",
                      "api: chip.set('asic','cells',filler,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: filler cell list",
                  "signature": [],
                  "switch": "-asic_cells_filler '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "hold": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_hold '*eco*'",
                      "api: chip.set('asic','cells',hold,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: hold cell list",
                  "signature": [],
                  "switch": "-asic_cells_hold '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "ignore": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_ignore '*eco*'",
                      "api: chip.set('asic','cells',ignore,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: ignore cell list",
                  "signature": [],
                  "switch": "-asic_cells_ignore '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "load": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_load '*eco*'",
                      "api: chip.set('asic','cells',load,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: load cell list",
                  "signature": [],
                  "switch": "-asic_cells_load '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "tap": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_tap '*eco*'",
                      "api: chip.set('asic','cells',tap,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: tap cell list",
                  "signature": [],
                  "switch": "-asic_cells_tap '<str>'",
                  "type": "[str]",
                  "value": []
              },
              "tie": {
                  "defvalue": [],
                  "example": [
                      "cli: -asic_cells_tie '*eco*'",
                      "api: chip.set('asic','cells',tie,'*eco*')"
                  ],
                  "help": "List of cells grouped by a property that can be accessed\ndirectly by the designer and tools. The example below shows how\nall cells containing the string 'eco' could be marked as dont use\nfor the tool.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: tie cell list",
                  "signature": [],
                  "switch": "-asic_cells_tie '<str>'",
                  "type": "[str]",
                  "value": []
              }
          },
          "corearea": {
              "defvalue": [],
              "example": [
                  "cli: -asic_corearea '(0,0)'",
                  "api: chip.set('asic', 'corearea', (0,0))"
              ],
              "help": "List of (x,y) points that define the outline of the core area for the\nphysical design. Simple rectangle areas can be defined with two points,\none for the lower left corner and one for the upper right corner. All\nvalues are specified in microns.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: core area outline",
              "signature": [],
              "switch": "-asic_corearea <[(float,float)]>",
              "type": "[(float,float)]",
              "unit": "um",
              "value": []
          },
          "coremargin": {
              "defvalue": null,
              "example": [
                  "cli: -asic_coremargin 1",
                  "api: chip.set('asic', 'coremargin', '1')"
              ],
              "help": "Halo/margin between the die boundary and core placement for\nautomated floorplanning when no diearea or floorplan is\nsupplied.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: block core margin",
              "signature": null,
              "switch": "-asic_coremargin <float>",
              "type": "float",
              "unit": "um",
              "value": null
          },
          "delaymodel": {
              "defvalue": null,
              "example": [
                  "cli: -asic_delaymodel ccs",
                  "api: chip.set('asic', 'delaymodel', 'ccs')"
              ],
              "help": "Delay model to use for the target libs. Supported values\nare nldm and ccs.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: delay model",
              "signature": null,
              "switch": "-asic_delaymodel <str>",
              "type": "str",
              "value": null
          },
          "density": {
              "defvalue": null,
              "example": [
                  "cli: -asic_density 30",
                  "api: chip.set('asic', 'density', '30')"
              ],
              "help": "Target density based on the total design cell area reported\nafter synthesis. This number is used when no diearea or floorplan is\nsupplied. Any number between 1 and 100 is legal, but values above 50\nmay fail due to area/congestion issues during apr.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: target core density",
              "signature": null,
              "switch": "-asic_density <float>",
              "type": "float",
              "value": null
          },
          "diearea": {
              "defvalue": [],
              "example": [
                  "cli: -asic_diearea '(0,0)'",
                  "api: chip.set('asic', 'diearea', (0,0))"
              ],
              "help": "List of (x,y) points that define the outline of the die area for the\nphysical design. Simple rectangle areas can be defined with two points,\none for the lower left corner and one for the upper right corner. All\nvalues are specified in microns.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: die area outline",
              "signature": [],
              "switch": "-asic_diearea <[(float,float)]>",
              "type": "[(float,float)]",
              "unit": "um",
              "value": []
          },
          "dir": {
              "default": {
                  "default": {
                      "defvalue": [],
                      "example": [
                          "cli: -asic_dir 'lib atool db ~/libdb'",
                          "api: chip.set('asic','dir','atool','db','~/libdb')"
                      ],
                      "help": "List of named dirs specified on a per tool basis. The parameter\nshould only be used for specifying dirs that are not directly\nsupported by the ASIC schema.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: special directory",
                      "signature": [],
                      "switch": "-asic_dir 'tool key <dir>'",
                      "type": "[dir]",
                      "value": []
                  }
              }
          },
          "file": {
              "default": {
                  "default": {
                      "author": [],
                      "copy": "false",
                      "date": [],
                      "defvalue": [],
                      "example": [
                          "cli: -asic_file 'yosys presyn ~/presyn.tcl'",
                          "api: chip.set('asic','file','yosys','presyn','~/presyn.tcl')"
                      ],
                      "filehash": [],
                      "hashalgo": "sha256",
                      "help": "List of named files specified on a per tool basis.\nThe parameter should only be used for specifying files that are\nnot directly supported by the ASIC schema.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: special file",
                      "signature": [],
                      "switch": "-asic_file 'tool key<file>'",
                      "type": "[file]",
                      "value": []
                  }
              }
          },
          "footprint": {
              "default": {
                  "alias": {
                      "defvalue": [],
                      "example": [
                          "cli: -asic_footprint_alias '12track FreeCell'",
                          "api: chip.set('asic','footprint','12track','alias','FreeCell')"
                      ],
                      "help": "Alias for the footprint key that is sometimes needed when the footprint can\nbe referenced by multiple names. The key is the 'official' footprint.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: Footprint name aliases",
                      "signature": [],
                      "switch": "-asic_footprint_alias 'key <str>'",
                      "type": "[str]",
                      "value": []
                  },
                  "size": {
                      "defvalue": null,
                      "example": [
                          "cli: -asic_footprint_size 'core (1.0,1.0)'",
                          "api: chip.set('asic','footprint','core','size',(1.0,1.0))"
                      ],
                      "help": "Size of the footprint described as a (width, height) tuple in\nmicrons.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: Footprint size",
                      "signature": null,
                      "switch": "-asic_footprint_size 'key <str>'",
                      "type": "(float,float)",
                      "value": null
                  },
                  "symmetry": {
                      "defvalue": null,
                      "example": [
                          "cli: -asic_footprint_symmetry 'core X Y'",
                          "api: chip.set('asic','footprint','core','symmetry','X Y')"
                      ],
                      "help": "Footprint symmetry based on LEF standard definition. 'X' implies\nsymmetric about the x axis, 'Y' implies symmetry about the y axis, and\n'X Y' implies symmetry about the x and y axis.",
                      "lock": "false",
                      "notes": null,
                      "require": null,
                      "scope": "job",
                      "shorthelp": "ASIC: Footprint symmetry",
                      "signature": null,
                      "switch": "-asic_footprint_symmetry 'key <str>'",
                      "type": "str",
                      "value": null
                  }
              }
          },
          "hpinlayer": {
              "defvalue": null,
              "example": [
                  "cli: -asic_hpinlayer m4",
                  "api: chip.set('asic', 'hpinlayer', 'm4')"
              ],
              "help": "Metal layer to use for automated horizontal pin placement\nduring APR.  The metal layers can be specified as technology\nagnostic SC layers starting with m1 or as hard PDK specific\nlayer names.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: vertical pin layer",
              "signature": null,
              "switch": "-asic_hpinlayer <str>",
              "type": "str",
              "value": null
          },
          "libarch": {
              "defvalue": null,
              "example": [
                  "cli: -asic_libarch '12track'",
                  "api: chip.set('asic','libarch','12track')"
              ],
              "help": "The library architecture (e.g. library height) used to build the\ndesign. For example a PDK with support for 9 and 12 track libraries\nmight have 'libarchs' called 9t and 12t.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: library architecture",
              "signature": null,
              "switch": "-asic_libarch '<str>'",
              "type": "str",
              "value": null
          },
          "logiclib": {
              "defvalue": [],
              "example": [
                  "cli: -asic_logiclib nangate45",
                  "api: chip.set('asic', 'logiclib','nangate45')"
              ],
              "help": "List of all selected logic libraries libraries\nto use for optimization for a given library architecture\n(9T, 11T, etc).",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: logic libraries",
              "signature": [],
              "switch": "-asic_logiclib <str>",
              "type": "[str]",
              "value": []
          },
          "macrolib": {
              "defvalue": [],
              "example": [
                  "cli: -asic_macrolib sram64x1024",
                  "api: chip.set('asic', 'macrolib','sram64x1024')"
              ],
              "help": "List of macro libraries to be linked in during synthesis and place\nand route. Macro libraries are used for resolving instances but are\nnot used as targets for logic synthesis.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: macro libraries",
              "signature": [],
              "switch": "-asic_macrolib <str>",
              "type": "[str]",
              "value": []
          },
          "maxcap": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxcap '0.25e-12'",
                  "api: chip.set('asic', 'maxcap', '0.25e-12')"
              ],
              "help": "Maximum allowed capacitance per net.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum net capacitance",
              "signature": null,
              "switch": "-asic_maxcap <float>",
              "type": "float",
              "unit": "F",
              "value": null
          },
          "maxfanout": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxfanout 64",
                  "api: chip.set('asic', 'maxfanout', '64')"
              ],
              "help": " Maximum driver fanout allowed during automated place and route.\nThe parameter directs the APR tool to break up any net with fanout\nlarger than maxfanout into sub nets and buffer.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum fanout",
              "signature": null,
              "switch": "-asic_maxfanout <int>",
              "type": "int",
              "value": null
          },
          "maxlayer": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxlayer m2",
                  "api: chip.set('asic', 'maxlayer', 'm2')"
              ],
              "help": "Maximum SC metal layer name to be used for automated place and route .\nAlternatively the layer can be a string that matches a layer hard coded\nin the pdk_aprtech file. Designers wishing to use the same setup across\nmultiple process nodes should use the integer approach. For processes\nwith ambiguous starting routing layers, exact strings should be used.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum routing layer",
              "signature": null,
              "switch": "-asic_maxlayer <str>",
              "type": "str",
              "value": null
          },
          "maxlength": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxlength 1000",
                  "api: chip.set('asic', 'maxlength', '1000')"
              ],
              "help": "Maximum total wire length allowed in design during APR. Any\nnet that is longer than maxlength is broken up into segments by\nthe tool.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum wire length",
              "signature": null,
              "switch": "-asic_maxlength <float>",
              "type": "float",
              "unit": "um",
              "value": null
          },
          "maxslew": {
              "defvalue": null,
              "example": [
                  "cli: -asic_maxslew '0.25e-9'",
                  "api: chip.set('asic', 'maxslew', '0.25e-9')"
              ],
              "help": "Maximum allowed transition time per net.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: maximum slew",
              "signature": null,
              "switch": "-asic_maxslew <float>",
              "type": "float",
              "unit": "s",
              "value": null
          },
          "minlayer": {
              "defvalue": null,
              "example": [
                  "cli: -asic_minlayer m2",
                  "api: chip.set('asic', 'minlayer', 'm2')"
              ],
              "help": "Minimum SC metal layer name to be used for automated place and route .\nAlternatively the layer can be a string that matches a layer hard coded\nin the pdk_aprtech file. Designers wishing to use the same setup across\nmultiple process nodes should use the integer approach. For processes\nwith ambiguous starting routing layers, exact strings should be used.",
              "lock": "false",
              "notes": null,
              "require": null,
              "scope": "job",
              "shorthelp": "ASIC: minimum routing layer",
              "signature": null,
              "switch": "-asic_minlayer <str>",
              "type": "str",
              "value": null
          },
          "ndr": {
              "default": {
                  "defvalue": null,
                  "example": [
                      "cli: -asic_ndr 'clk (0.2,0.2)'",
                      "api: chip.set('asic','ndr','clk', (0.2,0.2))"
                  ],
                  "help": "Definitions of non-default routing rule specified on a per\nnet basis. Constraints are entered as a (width,space) tuples\nspecified in microns.",
                  "lock": "false",
                  "notes": null,
                  "require": null,
                  "scope": "job",
                  "shorthelp": "ASIC: non-default routing rule",
                  "signature": null,
                  "switch": "-asic_ndr 'netname <(float,float)>",
                  "type": "(float,float)",
                  "value": null
              }
          },
          "pdk": {
              "defvalue": null,
              "example": [
                  "cli: -asic_pdk freepdk45",
                  "api: chip.set('asic','pdk','freepdk45')"
              ],
              "help": "Target ASIC PDK to use in the design.",
              "lock": "false",
              "notes": null,
              "require": null,
             
  
  [... the rest is omitted ...]
files.old_path: tests/core/data/defaults.json