files: '''Sphinx extension that provides directives for automatically generating
  documentation for the three types of dynamically loaded modules used by SC:
  flows, foundries, and tools.
  '''
  
  from docutils import nodes
  from sphinx.util.nodes import nested_parse_with_titles
  from docutils.statemachine import ViewList
  from sphinx.util.docutils import SphinxDirective
  import docutils
  from docutils.parsers.rst import directives
  
  import copy
  import importlib
  import pkgutil
  import os
  import sys
  import subprocess
  
  from common import *
  
  from siliconcompiler import utils
  
  #############
  # Helpers
  #############
  
  # We need this in a few places, so just make it global
  SC_ROOT = os.path.abspath(f'{__file__}/../../../')
  
  def build_schema_value_table(schema, keypath_prefix=[], skip_zero_weight=False):
      '''Helper function for displaying values set in schema as a docutils table.'''
      table = [[strong('Keypath'), strong('Value')]]
      flat_cfg = flatten(schema)
      for keys, val in flat_cfg.items():
          full_keypath = list(keypath_prefix) + list(keys)
  
          if (skip_zero_weight and
              len(full_keypath) == 6 and full_keypath[0] == 'flowgraph' and full_keypath[-2] == 'weight' and
              'value' in val and val['value'] == '0'):
              continue
  
          keypath = ', '.join(full_keypath)
  
          if 'value' in val and val['value']:
              # Don't display false booleans
              if val['type'] == 'bool' and val['value'] == 'false':
                  continue
              if val['type'].startswith('['):
                  if len(val['value']) > 1:
                      val_node = build_list([code(v) for v in val['value']])
                  elif len(val['value']) > 0:
                      val_node = code(val['value'][0])
                  else:
                      val_node = para('')
              else:
                  val_node = code(val['value'])
  
              table.append([code(keypath), val_node])
  
      if len(table) > 1:
          return build_table(table)
      else:
          return None
  
  def build_config_recursive(schema, keypath_prefix=[], sec_key_prefix=[]):
      '''Helper function for displaying schema at each level as tables unde nested
      sections.
  
      For each item:
      - If it's a leaf, collect it into a table we will display at this
          level
      - Otherwise, recurse and collect sections of lower levels
      '''
      leaves = {}
      child_sections = []
      for key, val in schema.items():
          if key == 'default': continue
          if 'help' in val:
              if 'value' in val and val['value']:
                  leaves.update({key: val})
          else:
              children = build_config_recursive(val, keypath_prefix=keypath_prefix+[key], sec_key_prefix=sec_key_prefix)
              child_sections.extend(children)
  
      # If we've found leaves, create a new section where we'll display a
      # table plus all child sections.
      if len(leaves) > 0:
          keypath = ', '.join(keypath_prefix)
          section_key = '-'.join(sec_key_prefix + keypath_prefix)
          top = build_section(keypath, section_key)
          top += build_schema_value_table(leaves, keypath_prefix=keypath_prefix)
          top += child_sections
          return [top]
      else:
          # Otherwise, just pass on the child sections -- we don't want to
          # create an extra level of section hierarchy for levels of the
          # schema without leaves.
          return child_sections
  
  #############
  # Base class
  #############
  
  def flag_opt(argument):
      if argument is not None:
          raise ValueError('Flag should not have content')
      return True
  
  class DynamicGen(SphinxDirective):
      '''Base class for all three directives provided by this extension.
  
      Each child class implements a directive by overriding the display_config()
      method and setting a PATH member variable.
      '''
  
      option_spec = {'nobuiltins': flag_opt}
  
      def document_module(self, module, modname, path):
          '''Build section documenting given module and name.'''
          s = build_section_with_target(modname, f'{modname}-ref', self.state.document)
  
          if not hasattr(module, 'make_docs'):
              return None
  
          make_docs = getattr(module, 'make_docs')
  
          # raw docstrings have funky indentation (basically, each line is already
          # indented as much as the function), so we call trim() helper function
          # to clean it up
          docstr = utils.trim(make_docs.__doc__)
  
          if docstr:
              self.parse_rst(docstr, s)
  
          builtin = os.path.abspath(path).startswith(SC_ROOT)
  
          if builtin:
              relpath = path[len(SC_ROOT)+1:]
              gh_root = 'https://github.com/siliconcompiler/siliconcompiler/blob/main'
              gh_link = f'{gh_root}/{relpath}'
              filename = os.path.basename(relpath)
              p = para('Setup file: ')
              p += link(gh_link, text=filename)
              s += p
  
          chip = make_docs()
  
          extra_content = self.extra_content(chip, modname)
          if extra_content is not None:
              s += extra_content
  
          s += self.display_config(chip, modname)
  
          return s
  
      def run(self):
          '''Main entry point of directive.'''
          sections = []
  
          for module, modname in self.get_modules():
              path = module.__file__
              self.env.note_dependency(path)
              docs = self.document_module(module, modname, path)
              if docs is not None:
                  sections.append((docs, modname))
  
          if len(sections) > 0:
              # Sort sections by module name
              sections = sorted(sections, key=lambda t: t[1])
              # Strip off modname so we just return list of docutils sections
              sections, _ = zip(*sections)
  
          return list(sections)
  
      def get_modules(self):
          '''Gets dynamic modules under `self.PATH`.
  
          This function explicitly searches builtins as well as SCPATH
          directories. Although the directory for builtin tools gets added to
          SCPATH after a chip object has been initialized, we can't rely on this
          since we can't be sure that's happened yet. Therefore, we have to check
          each one explicitly.
  
          However, this could result in duplicate modules being detected once the
          SCPATH does get updated. Therefore, we check to ensure that SCPATH
          directories are not equal to the builtins directory before searching it.
  
          TODO: we want better duplicate resolution (in case the user explicitly
          declares a duplicate tool), where SCPATH takes priority.
          '''
          builtins_dir = f'{SC_ROOT}/siliconcompiler/{self.PATH}'
          if 'nobuiltins' not in self.options:
              modules = self.get_modules_in_dir(builtins_dir)
          else:
              modules = []
  
          if 'SCPATH' in os.environ:
              scpaths = os.environ['SCPATH'].split(':')
              for scpath in scpaths:
                  user_dir = f'{scpath}/{self.PATH}'
                  if not os.path.isdir(user_dir) or builtins_dir == user_dir:
                      continue
                  modules.extend(self.get_modules_in_dir(user_dir))
  
          return modules
  
      def get_modules_in_dir(self, module_dir):
          '''Routine for getting modules and their names from a certain
          directory.'''
          modules = []
          for importer, modname, _ in pkgutil.iter_modules([module_dir]):
              if modname in ('sc_floorplan'):
                  continue
              module = importer.find_module(modname).load_module(modname)
              modules.append((module, modname))
  
          return modules
  
      def parse_rst(self, content, s):
          '''Helper for parsing reStructuredText content, adding it directly to
          section `s`.'''
          rst = ViewList()
          # use fake filename 'inline' for error # reporting
          for i, line in enumerate(content.split('\n')):
              rst.append(line, 'inline', i)
          nested_parse_with_titles(self.state, rst, s)
  
      def extra_content(self, chip, modname):
          '''Adds extra content to documentation.
  
          May return a list of docutils nodes that will be added to the
          documentation in between a module's docstring and configuration table.
          Otherwise, if return value is None, don't add anything.
          '''
          return None
  
  #########################
  # Specialized extensions
  #########################
  
  class FlowGen(DynamicGen):
      PATH = 'flows'
  
      def extra_content(self, chip, modname):
          flow_path = os.path.join(self.env.app.outdir, f'_images/gen/{modname}.svg')
          #chip.write_flowgraph(flow_path, fillcolor='#1c4587', fontcolor
  
  [... the rest is omitted ...]
files: docs/_ext/dynamicgen.py
files: '''Sphinx extension that provides directives for automatically generating
  documentation for the three types of dynamically loaded modules used by SC:
  flows, foundries, and tools.
  '''
  
  from docutils import nodes
  from sphinx.util.nodes import nested_parse_with_titles
  from docutils.statemachine import ViewList
  from sphinx.util.docutils import SphinxDirective
  import docutils
  from docutils.parsers.rst import directives
  
  import copy
  import importlib
  import pkgutil
  import os
  import sys
  import subprocess
  
  from common import *
  
  import siliconcompiler
  
  #############
  # Helpers
  #############
  
  # We need this in a few places, so just make it global
  SC_ROOT = os.path.abspath(f'{__file__}/../../../')
  
  def build_schema_value_table(schema, keypath_prefix=[], skip_zero_weight=False):
      '''Helper function for displaying values set in schema as a docutils table.'''
      table = [[strong('Keypath'), strong('Value')]]
      flat_cfg = flatten(schema)
      for keys, val in flat_cfg.items():
          full_keypath = list(keypath_prefix) + list(keys)
  
          if (skip_zero_weight and
              len(full_keypath) == 6 and full_keypath[0] == 'flowgraph' and full_keypath[-2] == 'weight' and
              'value' in val and val['value'] == '0'):
              continue
  
          keypath = ', '.join(full_keypath)
  
          if 'value' in val and val['value']:
              # Don't display false booleans
              if val['type'] == 'bool' and val['value'] == 'false':
                  continue
              if val['type'].startswith('['):
                  if len(val['value']) > 1:
                      val_node = build_list([code(v) for v in val['value']])
                  elif len(val['value']) > 0:
                      val_node = code(val['value'][0])
                  else:
                      val_node = para('')
              else:
                  val_node = code(val['value'])
  
              table.append([code(keypath), val_node])
  
      if len(table) > 1:
          return build_table(table)
      else:
          return None
  
  def build_config_recursive(schema, keypath_prefix=[], sec_key_prefix=[]):
      '''Helper function for displaying schema at each level as tables unde nested
      sections.
  
      For each item:
      - If it's a leaf, collect it into a table we will display at this
          level
      - Otherwise, recurse and collect sections of lower levels
      '''
      leaves = {}
      child_sections = []
      for key, val in schema.items():
          if key == 'default': continue
          if 'help' in val:
              if 'value' in val and val['value']:
                  leaves.update({key: val})
          else:
              children = build_config_recursive(val, keypath_prefix=keypath_prefix+[key], sec_key_prefix=sec_key_prefix)
              child_sections.extend(children)
  
      # If we've found leaves, create a new section where we'll display a
      # table plus all child sections.
      if len(leaves) > 0:
          keypath = ', '.join(keypath_prefix)
          section_key = '-'.join(sec_key_prefix + keypath_prefix)
          top = build_section(keypath, section_key)
          top += build_schema_value_table(leaves, keypath_prefix=keypath_prefix)
          top += child_sections
          return [top]
      else:
          # Otherwise, just pass on the child sections -- we don't want to
          # create an extra level of section hierarchy for levels of the
          # schema without leaves.
          return child_sections
  
  def trim(docstring):
      '''Helper function for cleaning up indentation of docstring.
  
      This is important for properly parsing complex RST in our docs.
  
      Source:
      https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation'''
      if not docstring:
          return ''
      # Convert tabs to spaces (following the normal Python rules)
      # and split into a list of lines:
      lines = docstring.expandtabs().splitlines()
      # Determine minimum indentation (first line doesn't count):
      indent = sys.maxsize
      for line in lines[1:]:
          stripped = line.lstrip()
          if stripped:
              indent = min(indent, len(line) - len(stripped))
      # Remove indentation (first line is special):
      trimmed = [lines[0].strip()]
      if indent < sys.maxsize:
          for line in lines[1:]:
              trimmed.append(line[indent:].rstrip())
      # Strip off trailing and leading blank lines:
      while trimmed and not trimmed[-1]:
          trimmed.pop()
      while trimmed and not trimmed[0]:
          trimmed.pop(0)
      # Return a single string:
      return '\n'.join(trimmed)
  
  #############
  # Base class
  #############
  
  def flag_opt(argument):
      if argument is not None:
          raise ValueError('Flag should not have content')
      return True
  
  class DynamicGen(SphinxDirective):
      '''Base class for all three directives provided by this extension.
  
      Each child class implements a directive by overriding the display_config()
      method and setting a PATH member variable.
      '''
  
      option_spec = {'nobuiltins': flag_opt}
  
      def document_module(self, module, modname, path):
          '''Build section documenting given module and name.'''
          s = build_section_with_target(modname, f'{modname}-ref', self.state.document)
  
          if not hasattr(module, 'make_docs'):
              return None
  
          make_docs = getattr(module, 'make_docs')
  
          # raw docstrings have funky indentation (basically, each line is already
          # indented as much as the function), so we call trim() helper function
          # to clean it up
          docstr = trim(make_docs.__doc__)
  
          if docstr:
              self.parse_rst(docstr, s)
  
          builtin = os.path.abspath(path).startswith(SC_ROOT)
  
          if builtin:
              relpath = path[len(SC_ROOT)+1:]
              gh_root = 'https://github.com/siliconcompiler/siliconcompiler/blob/main'
              gh_link = f'{gh_root}/{relpath}'
              filename = os.path.basename(relpath)
              p = para('Setup file: ')
              p += link(gh_link, text=filename)
              s += p
  
          chip = make_docs()
  
          extra_content = self.extra_content(chip, modname)
          if extra_content is not None:
              s += extra_content
  
          s += self.display_config(chip, modname)
  
          return s
  
      def run(self):
          '''Main entry point of directive.'''
          sections = []
  
          for module, modname in self.get_modules():
              path = module.__file__
              self.env.note_dependency(path)
              docs = self.document_module(module, modname, path)
              if docs is not None:
                  sections.append((docs, modname))
  
          if len(sections) > 0:
              # Sort sections by module name
              sections = sorted(sections, key=lambda t: t[1])
              # Strip off modname so we just return list of docutils sections
              sections, _ = zip(*sections)
  
          return list(sections)
  
      def get_modules(self):
          '''Gets dynamic modules under `self.PATH`.
  
          This function explicitly searches builtins as well as SCPATH
          directories. Although the directory for builtin tools gets added to
          SCPATH after a chip object has been initialized, we can't rely on this
          since we can't be sure that's happened yet. Therefore, we have to check
          each one explicitly.
  
          However, this could result in duplicate modules being detected once the
          SCPATH does get updated. Therefore, we check to ensure that SCPATH
          directories are not equal to the builtins directory before searching it.
  
          TODO: we want better duplicate resolution (in case the user explicitly
          declares a duplicate tool), where SCPATH takes priority.
          '''
          builtins_dir = f'{SC_ROOT}/siliconcompiler/{self.PATH}'
          if 'nobuiltins' not in self.options:
              modules = self.get_modules_in_dir(builtins_dir)
          else:
              modules = []
  
          if 'SCPATH' in os.environ:
              scpaths = os.environ['SCPATH'].split(':')
              for scpath in scpaths:
                  user_dir = f'{scpath}/{self.PATH}'
                  if not os.path.isdir(user_dir) or builtins_dir == user_dir:
                      continue
                  modules.extend(self.get_modules_in_dir(user_dir))
  
          return modules
  
      def get_modules_in_dir(self, module_dir):
          '''Routine for getting modules and their names from a certain
          directory.'''
          modules = []
          for importer, modname, _ in pkgutil.iter_modules([module_dir]):
              if modname in ('sc_floorplan'):
                  continue
              module =
  
  [... the rest is omitted ...]
files: docs/_ext/dynamicgen.py
files: from docutils import nodes
  from sphinx.util.nodes import nested_parse_with_titles
  from docutils.statemachine import ViewList
  from sphinx.util.docutils import SphinxDirective
  from siliconcompiler.schema import schema_cfg
  
  from common import *
  
  # Main Sphinx plugin
  class SchemaGen(SphinxDirective):
  
      def run(self):
          self.env.note_dependency('../siliconcompiler/schema.py')
  
          schema = schema_cfg()
  
          # Split up schema into root and nested keys so that we can add a custom
          # header to the root entries.
          basic_schema = {}
          nested_schema = {}
          for key, val in schema.items():
              if is_leaf(val):
                  basic_schema[key] = val
              else:
                  nested_schema[key] = val
  
          basic_section = build_section('root', 'root')
          for n in self.process_schema(basic_schema):
              basic_section += n
  
          nested_sections = self.process_schema(nested_schema)
  
          return [basic_section] + nested_sections
  
      def process_schema(self, schema, parents=[]):
          if 'help' in schema:
              entries = [[strong('Description'),   para(schema['shorthelp'])],
                         [strong('Type'),          para(schema['type'])],
                         [strong('Default Value'), para(schema['defvalue'])],
                         [strong('CLI Switch'),    code(schema['switch'])]]
              for example in schema['example']:
                  name, ex = example.split(':', 1)
                  entries.append([strong(f'Example ({name.upper()})'), code(ex.strip())])
  
              table = build_table(entries)
              body = self.parse_rst(schema['help'])
  
              return [table, body]
          else:
              sections = []
              for key in schema.keys():
                  if key == 'default':
                      for n in self.process_schema(schema['default'], parents=parents):
                          sections.append(n)
                  else:
                      section_key = '-'.join(parents) + '-' + key
                      section = build_section(key, section_key)
                      for n in self.process_schema(schema[key], parents=parents+[key]):
                          section += n
                      sections.append(section)
  
              # Sort all sections alphabetically by title. We may also have nodes
              # in this list that aren't sections if  `schema` has a 'default'
              # entry that's a leaf. In this case, we sort this as an empty string
              # in order to put this node at the beginning of the list.
              return sorted(sections, key=lambda s: s[0][0] if isinstance(s, nodes.section) else '')
  
      def parse_rst(self, content):
          rst = ViewList()
          # use fake filename 'inline' for error # reporting
          for i, line in enumerate(content.split('\n')):
              rst.append(line, 'inline', i)
          body = nodes.paragraph()
          nested_parse_with_titles(self.state, rst, body)
  
          return body
  
  
  def setup(app):
      app.add_directive('schemagen', SchemaGen)
  
      return {
          'version': '0.1',
          'parallel_read_safe': True,
          'parallel_write_safe': True,
      }
  
  
  [... the rest is omitted ...]
files: docs/_ext/schemagen.py
files: from docutils import nodes
  from sphinx.util.nodes import nested_parse_with_titles
  from docutils.statemachine import ViewList
  from sphinx.util.docutils import SphinxDirective
  from siliconcompiler.schema import schema_cfg
  
  from common import *
  
  # Main Sphinx plugin
  class SchemaGen(SphinxDirective):
  
      def run(self):
          self.env.note_dependency('../siliconcompiler/schema.py')
  
          schema = schema_cfg()
  
          # Split up schema into root and nested keys so that we can add a custom
          # header to the root entries.
          basic_schema = {}
          nested_schema = {}
          for key, val in schema.items():
              if is_leaf(val):
                  basic_schema[key] = val
              else:
                  nested_schema[key] = val
  
          basic_section = build_section('root', 'root')
          for n in self.process_schema(basic_schema):
              basic_section += n
  
          nested_sections = self.process_schema(nested_schema)
  
          return [basic_section] + nested_sections
  
      def process_schema(self, schema, parents=[]):
          if 'help' in schema:
              entries = [[strong('Description'),   para(schema['shorthelp'])],
                         [strong('Type'),          para(schema['type'])],
                         [strong('Default Value'), para(schema['defvalue'])],
                         [strong('CLI Switch'),    code(schema['switch'])]]
              for example in schema['example']:
                  name, ex = example.split(':', 1)
                  entries.append([strong(f'Example ({name.upper()})'), code(ex.strip())])
  
              table = build_table(entries)
              body = self.parse_rst(schema['help'])
  
              return [table, body]
          else:
              sections = []
              for key in schema.keys():
                  if key == 'default':
                      for n in self.process_schema(schema['default'], parents=parents):
                          sections.append(n)
                  else:
                      section_key = '-'.join(parents) + '-' + key
                      section = build_section(key, section_key)
                      for n in self.process_schema(schema[key], parents=parents+[key]):
                          section += n
                      sections.append(section)
  
              # Sort all sections alphabetically by title. We may also have nodes
              # in this list that aren't sections if  `schema` has a 'default'
              # entry that's a leaf. In this case, we sort this as an empty string
              # in order to put this node at the beginning of the list.
              return sorted(sections, key=lambda s: s[0][0] if isinstance(s, nodes.section) else '')
  
      def parse_rst(self, content):
          rst = ViewList()
          # use fake filename 'inline' and fake line number '1' for error reporting
          rst.append(content, 'inline', 1)
          body = nodes.paragraph()
          nested_parse_with_titles(self.state, rst, body)
  
          return body
  
  
  def setup(app):
      app.add_directive('schemagen', SchemaGen)
  
      return {
          'version': '0.1',
          'parallel_read_safe': True,
          'parallel_write_safe': True,
      }
  
  
  [... the rest is omitted ...]
files: docs/_ext/schemagen.py
files: # Copyright 2020 Silicon Compiler Authors. All Rights Reserved.
  
  from siliconcompiler import utils
  
  import re
  import os
  import sys
  import copy
  import json
  
  #############################################################################
  # PARAM DEFINITION
  #############################################################################
  
  def scparam(cfg,
              keypath,
              sctype=None,
              require=None,
              defvalue=None,
              scope='global',
              copy='false',
              lock='false',
              hashalgo='sha256',
              signature=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,
                  signature=signature,
                  hashalgo=hashalgo,
                  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'
              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['type'] = sctype
          cfg['scope'] = scope
          cfg['require'] = require
          cfg['lock'] = lock
          cfg['switch'] = switch
          cfg['shorthelp'] = shorthelp
          cfg['example'] = example
          cfg['help'] = schelp
          cfg['defvalue'] = defvalue
          cfg['value'] = defvalue
          cfg['signature'] = signature
  
          # 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 = {}
  
      # Version handling
      cfg = schema_version(cfg, SCHEMA_VERSION)
  
      # Runtime options
      cfg = schema_options(cfg)
      cfg = schema_arg(cfg)
  
      # Primary sources
      cfg = schema_design(cfg)
  
      # Constraints
      #cfg = schema_constraints(cfg)
  
      # Project configuration
      cfg = schema_package(cfg, 'package')
      cfg = schema_checklist(cfg)
      cfg = schema_design(cfg)
      cfg = schema_read(cfg)
      cfg = schema_fpga(cfg)
      cfg = schema_asic(cfg)
      cfg = schema_mcmm(cfg)
  
      # Flow Information
      cfg = schema_flowgraph(cfg)
      cfg = schema_flowstatus(cfg)
      cfg = schema_eda(cfg)
  
      # PDK
      cfg = schema_pdk(cfg)
  
      # Package management
      cfg = schema_libs(cfg)
      cfg = schema_package(cfg, 'library')
      cfg = schema_checklist(cfg, 'library')
  
      # Compilation records
      cfg = schema_metric(cfg)
      cfg = schema_record(cfg)
  
      return cfg
  
  ###############################################################################
  # Minimal setup FPGA
  ###############################################################################
  
  def schema_version(cfg, version):
  
      scparam(cfg,['version', 'schema'],
              sctype='str',
              defvalue=version,
              shorthelp="Schema version number",
              switch="-version_schema <str>",
              example=["cli: -version_schema",
                       "api: chip.get('version', 'schema')"],
              schelp="""SiliconCompiler schema version number.""")
  
      scparam(cfg,['version', 'software'],
              sctype='str',
              shorthelp="Software version number",
              switch="-version_software <str>",
              example=["cli: -version_software",
                       "api: chip.get('version', 'software')"],
              schelp="""SiliconCompiler software version number.""")
  
      scparam(cfg,['version', 'print'],
              sctype='bool',
              shorthelp="Prints version number",
              switch="-version <bool>",
              example=["cli: -version",
                      "api: chip.get('version', 'print')"],
              schelp="""Command line switch to print the schema and software
              version numbers in an 'sc' command line app.""")
  
      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'
  
      scparam(cfg, ['pdk', 'foundry'],
              sctype='str',
              require="asic",
              short
  
  [... the rest is omitted ...]
files: siliconcompiler/schema.py
files: # Copyright 2020 Silicon Compiler Authors. All Rights Reserved.
  
  import re
  import os
  import sys
  import copy
  import json
  
  #############################################################################
  # PARAM DEFINITION
  #############################################################################
  
  def scparam(cfg,
              keypath,
              sctype=None,
              require=None,
              defvalue=None,
              scope='global',
              copy='false',
              lock='false',
              hashalgo='sha256',
              signature=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,
                  signature=signature,
                  hashalgo=hashalgo,
                  shorthelp=shorthelp,
                  switch=switch,
                  example=example,
                  schelp=schelp)
      else:
  
          # removing leading newline and space
          schelp = re.sub(r'\n\s*', " ", schelp)
          schelp = schelp.strip()
  
          # setting valus based on types
          # note (bools are never lists)
          if re.match(r'bool',sctype):
              require = 'all'
              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['type'] = sctype
          cfg['scope'] = scope
          cfg['require'] = require
          cfg['lock'] = lock
          cfg['switch'] = switch
          cfg['shorthelp'] = shorthelp
          cfg['example'] = example
          cfg['help'] = schelp
          cfg['defvalue'] = defvalue
          cfg['value'] = defvalue
          cfg['signature'] = signature
  
          # 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 = {}
  
      # Version handling
      cfg = schema_version(cfg, SCHEMA_VERSION)
  
      # Runtime options
      cfg = schema_options(cfg)
      cfg = schema_arg(cfg)
  
      # Primary sources
      cfg = schema_design(cfg)
  
      # Constraints
      #cfg = schema_constraints(cfg)
  
      # Project configuration
      cfg = schema_package(cfg, 'package')
      cfg = schema_checklist(cfg)
      cfg = schema_design(cfg)
      cfg = schema_read(cfg)
      cfg = schema_fpga(cfg)
      cfg = schema_asic(cfg)
      cfg = schema_mcmm(cfg)
  
      # Flow Information
      cfg = schema_flowgraph(cfg)
      cfg = schema_flowstatus(cfg)
      cfg = schema_eda(cfg)
  
      # PDK
      cfg = schema_pdk(cfg)
  
      # Package management
      cfg = schema_libs(cfg)
      cfg = schema_package(cfg, 'library')
      cfg = schema_checklist(cfg, 'library')
  
      # Compilation records
      cfg = schema_metric(cfg)
      cfg = schema_record(cfg)
  
      return cfg
  
  ###############################################################################
  # Minimal setup FPGA
  ###############################################################################
  
  def schema_version(cfg, version):
  
      scparam(cfg,['version', 'schema'],
              sctype='str',
              defvalue=version,
              shorthelp="Schema version number",
              switch="-version_schema <str>",
              example=["cli: -version_schema",
                       "api: chip.get('version', 'schema')"],
              schelp="""SiliconCompiler schema version number.""")
  
      scparam(cfg,['version', 'software'],
              sctype='str',
              shorthelp="Software version number",
              switch="-version_software <str>",
              example=["cli: -version_software",
                       "api: chip.get('version', 'software')"],
              schelp="""SiliconCompiler software version number.""")
  
      scparam(cfg,['version', 'print'],
              sctype='bool',
              shorthelp="Prints version number",
              switch="-version <bool>",
              example=["cli: -version",
                      "api: chip.get('version', 'print')"],
              schelp="""Command line switch to print the schema and software
              version numbers in an 'sc' command line app.""")
  
      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'
  
      scparam(cfg, ['pdk', 'foundry'],
              sctype='str',
              require="
  
  [... the rest is omitted ...]
files: siliconcompiler/schema.py
files: import os
  import shutil
  import sys
  
  def copytree(src, dst, ignore=[], dirs_exist_ok=False, link=False):
      '''Simple implementation of shutil.copytree to give us a dirs_exist_ok
      option in Python < 3.8.
  
      If link is True, create hard links in dst pointing to files in src
      instead of copying them.
      '''
      os.makedirs(dst, exist_ok=dirs_exist_ok)
  
      for name in os.listdir(src):
          if name in ignore:
              continue
  
          srcfile = os.path.join(src, name)
          dstfile = os.path.join(dst, name)
  
          if os.path.isdir(srcfile):
              copytree(srcfile, dstfile, ignore=ignore, dirs_exist_ok=dirs_exist_ok)
          elif link:
              os.link(srcfile, dstfile)
          else:
              shutil.copy2(srcfile, dstfile)
  
  def trim(docstring):
      '''Helper function for cleaning up indentation of docstring.
  
      This is important for properly parsing complex RST in our docs.
  
      Source:
      https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation'''
      if not docstring:
          return ''
      # Convert tabs to spaces (following the normal Python rules)
      # and split into a list of lines:
      lines = docstring.expandtabs().splitlines()
      # Determine minimum indentation (first line doesn't count):
      indent = sys.maxsize
      for line in lines[1:]:
          stripped = line.lstrip()
          if stripped:
              indent = min(indent, len(line) - len(stripped))
      # Remove indentation (first line is special):
      trimmed = [lines[0].strip()]
      if indent < sys.maxsize:
          for line in lines[1:]:
              trimmed.append(line[indent:].rstrip())
      # Strip off trailing and leading blank lines:
      while trimmed and not trimmed[-1]:
          trimmed.pop()
      while trimmed and not trimmed[0]:
          trimmed.pop(0)
      # Return a single string:
      return '\n'.join(trimmed)
  
  
  [... the rest is omitted ...]
files: siliconcompiler/utils.py
files: import os
  import shutil
  
  def copytree(src, dst, ignore=[], dirs_exist_ok=False, link=False):
      '''Simple implementation of shutil.copytree to give us a dirs_exist_ok
      option in Python < 3.8.
  
      If link is True, create hard links in dst pointing to files in src
      instead of copying them.
      '''
      os.makedirs(dst, exist_ok=dirs_exist_ok)
  
      for name in os.listdir(src):
          if name in ignore:
              continue
  
          srcfile = os.path.join(src, name)
          dstfile = os.path.join(dst, name)
  
          if os.path.isdir(srcfile):
              copytree(srcfile, dstfile, ignore=ignore, dirs_exist_ok=dirs_exist_ok)
          elif link:
              os.link(srcfile, dstfile)
          else:
              shutil.copy2(srcfile, dstfile)
  
  
  [... the rest is omitted ...]
files: siliconcompiler/utils.py