Tools#

SiliconCompiler execution depends on implementing adapter code (“drivers”) for each tool which gets called in a flowgraph. Tools are referenced as named modules by the flowgraph, and searched using the find_files() method. A complete set of supported tools can be found in Pre-Defined Tools.

Each tool can support multiple “tasks”. Each node in the flowgraph is associated with both a tool and a task. Shared configurations such as the tool’s minimum version number go in the tool modules, and task-specific settings go in the task modules.

For example, the KLayout tool can be used to export GDS files, display results in a GUI window, or take screenshots of a design. Each of those three functions is associated with a different task name: export, show, and screenshot respectively.

Each task has its own Python module and setup() method in the associated tool’s directory. Task modules also support the same pre_process() and post_process() functionality as tool scripts.

setup(chip)#

Tool and task setup is performed for each step and index within the run() function, prior to launching each individual task. Tools can be configured independently for different tasks (ie. the place task is different from the route task), so we need a method for passing information about the current step and index to the setup function. This is accomplished with the reserved parameters shown below.

step = chip.get('arg','step')
index = chip.get('arg','index')

Each node in the flowgraph has a step name, and an index. The step name is linked to a task type by the node() function, which is usually called in a siliconcompiler.Flow’s setup() function. The indices are used to allow multiple instances of a task to run in parallel with slightly different parameters. When you are not performing a parameter sweep, the “index” value will usually be set to "0".

All tools are required to bind the tool name to an executable name and to define any required command line options.

chip.set('tool', <toolname>, 'exe', <exename>)
chip.set('tool', <toolname>, 'task', <taskname> 'option', <option>)

For tools such as TCL based EDA tools, we also need to define the entry script and any associated script directories.

chip.set('tool', <toolname>, 'task', <taskname>, 'script', <entry_script>)
chip.set('tool', <toolname>, 'task', <taskname>, 'refdir', <scriptdir>)
chip.set('tool', <toolname>, 'task', <taskname>, 'format', <scriptformat>)

To leverage the run() function’s internal setup checking logic, it is highly recommend to define the parameter requirements, required inputs, expected output, version switch, and supported version numbers using the commands below:

chip.set('tool', <toolname>, 'task', <taskname>, 'input', <list[file]>)
chip.set('tool', <toolname>, 'task', <taskname>, 'output', <list[file]>)
chip.set('tool', <toolname>, 'task', <taskname>, 'require' <list[string]>)
chip.set('tool', <toolname>, 'task', <taskname>, 'report', <list[file]>)
chip.set('tool', <toolname>, 'version' <list[string]>)
chip.set('tool', <toolname>, 'vswitch', <string>)

parse_version(stdout)#

The run() function includes built in executable version checking, which can be disabled with the ['option', 'novercheck'] parameter. The executable option to use for printing out the version number is specified with the ['tool', <tool>, 'vswitch'] parameter within the setup() function. Commonly used options include ‘-v’, ‘--version’, ‘-version’. The executable output varies widely, so we need a parsing function that processes the output and returns a single uniform version string. The example shows how this function is implemented for the Yosys tool.

def parse_version(stdout):
    # Yosys 0.9+3672 (git sha1 014c7e26, gcc 7.5.0-3ubuntu1~18.04 -fPIC -Os)
    return stdout.split()[1]

The run() function compares the returned parsed version against the ['tool', <tool>, 'version'] parameter specified in the setup() function to ensure that a qualified executable version is being used.

normalize_version(version)#

SC’s version checking logic is based on Python’s PEP-440 standard. In order to perform version checking for tools that do not natively provide PEP-440 compatible version numbers, this function must be implemented to convert the tool-specific versions to a PEP-440 compatible equivalent.

Note that a raw version number may parse as a valid PEP-440 version but not be semantically correct. normalize_version() must be implemented in these cases to ensure version comparisons make sense. For example, we have to do this for Yosys.

def normalize_version(version):
    # Replace '+', which represents a "local version label", with '-', which is
    # an "implicit post release number".
    return version.replace('+', '-')

pre_process(chip)#

For certain tools and tasks, we may need to set some Schema parameters immediately before task execution. For example, we may want to set the die and core area before the floorplan step based on the area result from the synthesis step.

post_process(chip)#

The post process step is required to extract metrics from the tool log files. At a minimum the post process step should extract the number of warnings and errors from the tool log file and insert the value into the Schema. The post_process() logic is straight forward, but the regular expression logic can get involved for complex log files. Perhaps some day, EDA tools will produce SiliconCompiler compatible JSON metrics files.

The post_process function can also be used to post process the output data in the case of command line executable to produce an output that can be ingested by the SiliconCompiler framework. The Surelog post_process() implementation illustrates the power of the this functionality.

def post_process(chip):
  ''' Tool specific function to run after step execution
  '''
  design = chip.top()
  step = chip.get('arg', 'step')

  if step != 'import':
      return 0

  # Look in slpp_all/file_elab.lst for list of Verilog files included in
  # design, read these and concatenate them into one pickled output file.
  with open('slpp_all/file_elab.lst', 'r') as filelist, \
          open(f'outputs/{design}.v', 'w') as outfile:
      for path in filelist.read().split('\n'):
          if not path:
              # skip empty lines
              continue
          with open(path, 'r') as infile:
              outfile.write(infile.read())
          # in case end of file is missing a newline
          outfile.write('\n')

runtime_options(chip)#

The distributed execution model of SiliconCompiler mandates that absolute paths be resolved at task run time. The setup() function is run at run() launch to check flow validity, so we need a second function interface (runtime_options) to create the final commandline options. The runtime_options() function inspects the Schema and returns a cmdlist to be used by the ‘exe’ during task execution. The sequence of items used to generate the final command line invocation is as follows:

<'tool',...,'exe'> <'tool',...,'option'> <'tool',...,'script'> <runtime_options()>

The Surelog example below illustrates the process of defining a runtime_options function.

def runtime_options(chip):

  ''' Custom runtime options, returns list of command line options.
  '''

  step = chip.get('arg','step')
  index = chip.get('arg','index')

  cmdlist = []

  # source files
  for value in chip.find_files('option', 'ydir'):
      cmdlist.append('-y ' + value)
  for value in chip.find_files('option', 'vlib'):
      cmdlist.append('-v ' + value)
  for value in chip.find_files('option', 'idir'):
      cmdlist.append('-I' + value)
  for value in chip.get('option', 'define'):
      cmdlist.append('-D' + value)
  for value in chip.find_files('option', 'cmdfile'):
      cmdlist.append('-f ' + value)
  for value in chip.find_files('option', 'source'):
      cmdlist.append(value)

  cmdlist.append('-top ' + chip.top())
  # make sure we can find .sv files in ydirs
  cmdlist.append('+libext+.sv')

  # Set up user-provided parameters to ensure we elaborate the correct modules
  for param in chip.getkeys('option', 'param'):
      value = chip.get('option', 'param', param)
      cmdlist.append(f'-P{param}={value}')

  return cmdlist

make_docs()#

The SiliconCompiler includes automated document generators that search all tool modules for functions called make_docs(). It is highly recommended for all tools to include a make_docs() function. The function docstring is used for general narrative, while the body of the function is used to auto-generate a settings table based on the manifest created. At a minimum, the docstring should include a short description and links to the Documentation, Sources, and Installation. The example below shows the make_docs function for surelog.

def make_docs():
  '''
  Surelog is a SystemVerilog pre-processor, parser, elaborator,
  and UHDM compiler that provides IEEE design and testbench
  C/C++ VPI and a Python AST API.

  Documentation: https://github.com/chipsalliance/Surelog

  Sources: https://github.com/chipsalliance/Surelog

  Installation: https://github.com/chipsalliance/Surelog

  '''

  chip = siliconcompiler.Chip('<design>')
  chip.set('arg','step','import')
  chip.set('arg','index','0')
  setup(chip)
  return chip

run(chip)#

SiliconCompiler supports pure-Python tools that execute a Python function rather than an executable. To define a pure-Python tool, add a function called run() in your tool driver, which takes in a Chip object and implements your tool’s desired functionality. This function should return an integer exit code, with zero indicating success.

Note that pure-Python tool drivers still require a setup() function, but most ['tool', ...] fields will not be meaningful. At the moment, pure-Python tools do not support the following features:

  • Version checking

  • Replay scripts

  • Task timeout

  • Memory usage tracking

  • Breakpoints

  • Output redirection/regex-based logfile parsing

TCL interface#

Note

SiliconCompiler configuration settings are communicated to all script based tools as TCL nested dictionaries.

Schema configuration handoff from SiliconCompiler to script based tools is accomplished within the run() function by using the write_manifest() function to write out the complete schema as a nested TCL dictionary. A snippet of the resulting TCL dictionary is shown below.

dict set sc_cfg asic logiclib [list  NangateOpenCellLibrary ]
dict set sc_cfg asic macrolib [list ]
dict set sc_cfg design [list  gcd ]
dict set sc_cfg option frontend [list "verilog"]

This generated manifest also includes a helper function, sc_top, that handles the logic for determining the name of the design’s top-level module (mirroring the logic of top()).

It is the responsibility of the tool reference flow developer to bind the standardized SiliconCompiler TCL schema to the tool specific TCL commands and variables. The TCL snippet below shows how the OpenRoad TCL reference flow remaps the TCL nested dictionary to simple lists and scalars at the beginning of the flow for the sake of clarity.

#Design
set sc_design     [sc_top]
set sc_tool       <toolname>
set sc_optmode    [dict get $sc_cfg optmode]

# APR Parameters
set sc_mainlib     [lindex [dict get $sc_cfg asic logiclib] 0]
set sc_stackup     [dict get $sc_cfg option stackup]
set sc_targetlibs  [dict get $sc_cfg asic logiclib]
set sc_density     [dict get $sc_cfg constraint density]
set sc_pdk         [dict get $sc_cfg option pdk]
set sc_hpinmetal   [lindex [dict get $sc_cfg pdk $sc_pdk {var} $sc_tool pin_layer_horizontal $sc_stackup] 0]
set sc_vpinmetal   [lindex [dict get $sc_cfg pdk $sc_pdk {var} $sc_tool pin_layer_vertical $sc_stackup] 0]

Tool and Task Modules#

The table below shows the function interfaces supported in setting up tool and task logic.

Function

Description

Arg

Returns

Used by

Required

setup

Configures tool

Chip

n/a

run()

yes

runtime_options

Resolves paths at runtime

Chip

list

run()

no

parse_version

Returns executable version

stdout

version

run()

no

normalize_version

Returns executable version

tool version

normalized version

run()

no

pre_process

Pre-executable logic

Chip

n/a

run()

no

post_process

Post-executable logic

Chip

n/a

run()

no

make_docs

Doc generator

None

Chip

sphinx

no

run

Pure Python tool

Chip

exit code

run()

no

For a complete example of a tool setup module, see OpenROAD. For more in depth information about the various ['tool', ...] parameters, see the Schema section of the reference manual.