[util] Rejig how we load hjson configurations for dvsim.py
The main new feature from this patch is that a dvsim configuration
should now set a "dvsim_flow" value. This value is used to decide
which subclass of FlowCfg to construct (in CfgFactory.py).
There are two upsides to this:
1. You can now run a lint or synthesis run without specifying the
tool on the command line (before, the code made a SimCfg unless
it recognised the tool).
2. If you run dvsim.py on some other random hjson file, you get a
somewhat helpful error message. Before, you'd get something
cryptic about expanding verbosity flags.
There is also a downside:
1. Every configuration needs to specify dvsim_flow. In practice,
this isn't so bad, because this can be done in the included
common_*_cfg.hjson.
Note that "every configuration" here includes primary configurations.
This is kind of silly, because a "primary configuration" is really
just a list of other things to run. In later patches, we can split
these out into their own type, which should clean up quite a lot of
the code, and get rid of this requirement. However, you can't do that
splitting without the change in this patch (I tried!), so I've done
this patch first.
To make sense of how this all works:
- dvsim.py calls CfgFactory.make_config
- This uses CfgJson.load_hjson to load an hjson file and everything
it includes.
- After loading the file, make_config looks at dvsim_flow (which
must have a value) to decide which subclass of FlowCfg to
instantiate.
- The constructor for FlowCfg gets passed hjson_data. It sets up
a whole list of attributes, then calls self._merge_hjson to merge
the data from hjson_data into itself. It then calls self._expand
to expand all the wildcards. Subclasses can hook in to these two
methods if they need things to happen at specific times.
The only slight complication is from primary configs: configurations
that have a list of children to be loaded and run. These need to load
up some new hjson files. They can do so by calling back to the
factory (passed in as an argument to avoid circular dependencies).
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/util/dvsim/CfgFactory.py b/util/dvsim/CfgFactory.py
new file mode 100644
index 0000000..a258985
--- /dev/null
+++ b/util/dvsim/CfgFactory.py
@@ -0,0 +1,100 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+import logging as log
+import sys
+
+from CfgJson import load_hjson
+
+import FpvCfg
+import LintCfg
+import SimCfg
+import SynCfg
+
+
+def _load_cfg(path, initial_values):
+ '''Worker function for make_cfg.
+
+ initial_values is passed to load_hjson (see documentation there).
+
+ Returns a pair (cls, hjson_data) on success or raises a RuntimeError on
+ failure.
+
+ '''
+ # Start by loading up the hjson file and any included files
+ hjson_data = load_hjson(path, initial_values)
+
+ # Look up the value of flow in the loaded data. This is a required field,
+ # and tells us what sort of FlowCfg to make.
+ flow = hjson_data.get('flow')
+ if flow is None:
+ raise RuntimeError('{!r}: No value for the "flow" key. Are you sure '
+ 'this is a dvsim configuration file?'
+ .format(path))
+
+ classes = [
+ LintCfg.LintCfg,
+ SynCfg.SynCfg,
+ FpvCfg.FpvCfg,
+ SimCfg.SimCfg
+ ]
+ found_cls = None
+ known_types = []
+ for cls in classes:
+ assert cls.flow is not None
+ known_types.append(cls.flow)
+ if cls.flow == flow:
+ found_cls = cls
+ break
+ if found_cls is None:
+ raise RuntimeError('{}: Configuration file sets "flow" to {!r}, but '
+ 'this is not a known flow (known: {}).'
+ .format(path, flow, ', '.join(known_types)))
+
+ return (found_cls, hjson_data)
+
+
+def _make_child_cfg(path, args, initial_values):
+ try:
+ cls, hjson_data = _load_cfg(path, initial_values)
+ except RuntimeError as err:
+ log.error(str(err))
+ sys.exit(1)
+
+ # Since this is a child configuration (from some primary configuration),
+ # make sure that we aren't ourselves a primary configuration. We don't need
+ # multi-level hierarchies and this avoids circular dependencies.
+ if 'use_cfgs' in hjson_data:
+ raise RuntimeError('{}: Configuration file has use_cfgs, but is '
+ 'itself included from another configuration.'
+ .format(path))
+
+ # Call cls as a constructor. Note that we pass None as the mk_config
+ # argument: this is not supposed to load anything else.
+ return cls(path, hjson_data, args, None)
+
+
+def make_cfg(path, args, proj_root):
+ '''Make a flow config by loading the config file at path
+
+ args is the arguments passed to the dvsim.py tool and proj_root is the top
+ of the project.
+
+ '''
+ initial_values = {'proj_root': proj_root}
+ if args.tool is not None:
+ initial_values['tool'] = args.tool
+
+ try:
+ cls, hjson_data = _load_cfg(path, initial_values)
+ except RuntimeError as err:
+ log.error(str(err))
+ sys.exit(1)
+
+ def factory(child_path):
+ child_ivs = initial_values.copy()
+ child_ivs['flow'] = hjson_data['flow']
+ return _make_child_cfg(child_path, args, child_ivs)
+
+ return cls(path, hjson_data, args, factory)
diff --git a/util/dvsim/CfgJson.py b/util/dvsim/CfgJson.py
new file mode 100644
index 0000000..049e0c3
--- /dev/null
+++ b/util/dvsim/CfgJson.py
@@ -0,0 +1,172 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+'''A wrapper for loading hjson files as used by dvsim's FlowCfg'''
+
+from utils import parse_hjson, subst_wildcards
+
+
+# A set of fields that can be overridden on the command line and shouldn't be
+# loaded from the hjson in that case.
+_CMDLINE_FIELDS = {'tool'}
+
+
+def load_hjson(path, initial_values):
+ '''Load an hjson file and any includes
+
+ Combines them all into a single dictionary, which is then returned. This
+ does wildcard substitution on include names (since it might be needed to
+ find included files), but not otherwise.
+
+ initial_values is a starting point for the dictionary to be returned (which
+ is not modified). It needs to contain values for anything needed to resolve
+ include files (typically, this is 'proj_root' and 'tool' (if set)).
+
+ '''
+ worklist = [path]
+ seen = {path}
+ ret = initial_values.copy()
+ is_first = True
+
+ # Figure out a list of fields that had a value from the command line. These
+ # should have been passed in as part of initial_values and we need to know
+ # that we can safely ignore updates.
+ arg_keys = _CMDLINE_FIELDS & initial_values.keys()
+
+ while worklist:
+ next_path = worklist.pop()
+ new_paths = _load_single_file(ret, next_path, is_first, arg_keys)
+ if set(new_paths) & seen:
+ raise RuntimeError('{!r}: The file {!r} appears more than once '
+ 'when processing includes.'
+ .format(path, next_path))
+ seen |= set(new_paths)
+ worklist += new_paths
+ is_first = False
+
+ return ret
+
+
+def _load_single_file(target, path, is_first, arg_keys):
+ '''Load a single hjson file, merging its keys into target
+
+ Returns a list of further includes that should be loaded.
+
+ '''
+ hjson = parse_hjson(path)
+ if not isinstance(hjson, dict):
+ raise RuntimeError('{!r}: Top-level hjson object is not a dictionary.'
+ .format(path))
+
+ import_cfgs = []
+ for key, dict_val in hjson.items():
+ # If this key got set at the start of time and we want to ignore any
+ # updates: ignore them!
+ if key in arg_keys:
+ continue
+
+ # If key is 'import_cfgs', this should be a list. Add each item to the
+ # list of cfgs to process
+ if key == 'import_cfgs':
+ if not isinstance(dict_val, list):
+ raise RuntimeError('{!r}: import_cfgs value is {!r}, but '
+ 'should be a list.'
+ .format(path, dict_val))
+ import_cfgs += dict_val
+ continue
+
+ # 'use_cfgs' is a bit like 'import_cfgs', but is only used for primary
+ # config files (where it is a list of the child configs). This
+ # shouldn't be used except at top-level (the first configuration file
+ # to be loaded).
+ #
+ # If defined, check that it's a list, but then allow it to be set in
+ # the target dictionary as usual.
+ if key == 'use_cfgs':
+ if not is_first:
+ raise RuntimeError('{!r}: File is included by another one, '
+ 'but defines "use_cfgs".'
+ .format(path))
+ if not isinstance(dict_val, list):
+ raise RuntimeError('{!r}: use_cfgs must be a list. Saw {!r}.'
+ .format(path, dict_val))
+
+ # Otherwise, update target with this attribute
+ set_target_attribute(path, target, key, dict_val)
+
+ # Expand the names of imported configuration files as we return them
+ return [subst_wildcards(cfg_path,
+ target,
+ ignored_wildcards=[],
+ ignore_error=False)
+ for cfg_path in import_cfgs]
+
+
+def set_target_attribute(path, target, key, dict_val):
+ '''Set an attribute on the target dictionary
+
+ This performs checks for conflicting values and merges lists /
+ dictionaries.
+
+ '''
+ old_val = target.get(key)
+ if old_val is None:
+ # A new attribute (or the old value was None, in which case it's
+ # just a placeholder and needs writing). Set it and return.
+ target[key] = dict_val
+ return
+
+ if isinstance(old_val, list):
+ if not isinstance(dict_val, list):
+ raise RuntimeError('{!r}: Conflicting types for key {!r}: was '
+ '{!r}, a list, but loaded value is {!r}, '
+ 'of type {}.'
+ .format(path, key, old_val, dict_val,
+ type(dict_val).__name__))
+
+ # Lists are merged by concatenation
+ target[key] += dict_val
+ return
+
+ # The other types we support are "scalar" types.
+ scalar_types = [(str, [""]), (int, [0, -1]), (bool, [False])]
+ defaults = None
+ for st_type, st_defaults in scalar_types:
+ if isinstance(dict_val, st_type):
+ defaults = st_defaults
+ break
+ if defaults is None:
+ raise RuntimeError('{!r}: Value for key {!r} is {!r}, of '
+ 'unknown type {}.'
+ .format(path, key, dict_val,
+ type(dict_val).__name__))
+ if not isinstance(old_val, st_type):
+ raise RuntimeError('{!r}: Value for key {!r} is {!r}, but '
+ 'we already had the value {!r}, of an '
+ 'incompatible type.'
+ .format(path, key, dict_val, old_val))
+
+ # The types are compatible. If the values are equal, there's nothing more
+ # to do
+ if old_val == dict_val:
+ return
+
+ old_is_default = old_val in defaults
+ new_is_default = dict_val in defaults
+
+ # Similarly, if new value looks like a default, ignore it (regardless
+ # of whether the current value looks like a default).
+ if new_is_default:
+ return
+
+ # If the existing value looks like a default and the new value doesn't,
+ # take the new value.
+ if old_is_default:
+ target[key] = dict_val
+ return
+
+ # Neither value looks like a default. Raise an error.
+ raise RuntimeError('{!r}: Value for key {!r} is {!r}, but '
+ 'we already had a conflicting value of {!r}.'
+ .format(path, key, dict_val, old_val))
diff --git a/util/dvsim/FlowCfg.py b/util/dvsim/FlowCfg.py
index 6d1a58f..88ccc36 100644
--- a/util/dvsim/FlowCfg.py
+++ b/util/dvsim/FlowCfg.py
@@ -12,25 +12,39 @@
import hjson
+from CfgJson import set_target_attribute
from Deploy import Deploy
-from utils import VERBOSE, md_results_to_html, parse_hjson, subst_wildcards
-
-# A set of fields that can be overridden on the command line.
-_CMDLINE_FIELDS = {'tool', 'verbosity'}
+from utils import (VERBOSE, md_results_to_html,
+ subst_wildcards, find_and_substitute_wildcards)
# Interface class for extensions.
class FlowCfg():
+ '''Base class for the different flows supported by dvsim.py
+
+ The constructor expects some parsed hjson data. Create these objects with
+ the factory function in CfgFactory.py, which loads the hjson data and picks
+ a subclass of FlowCfg based on its contents.
+
+ '''
+
+ # Set in subclasses. This is the key that must be used in an hjson file to
+ # tell dvsim.py which subclass to use.
+ flow = None
+
+ # Can be overridden in subclasses to configure which wildcards to ignore
+ # when expanding hjson.
+ ignored_wildcards = []
+
def __str__(self):
return pprint.pformat(self.__dict__)
- def __init__(self, flow_cfg_file, proj_root, args):
+ def __init__(self, flow_cfg_file, hjson_data, args, mk_config):
# Options set from command line
self.items = args.items
self.list_items = args.list
self.select_cfgs = args.select_cfgs
self.flow_cfg_file = flow_cfg_file
- self.proj_root = proj_root
self.args = args
self.scratch_root = args.scratch_root
self.branch = args.branch
@@ -40,9 +54,6 @@
self.project = ""
self.scratch_path = ""
- # Imported cfg files using 'import_cfgs' keyword
- self.imported_cfg_files = [flow_cfg_file]
-
# Add exports using 'exports' keyword - these are exported to the child
# process' environment.
self.exports = []
@@ -95,7 +106,68 @@
self.email_summary_md = ""
self.results_summary_md = ""
- def __post_init__(self):
+ # Merge in the values from the loaded hjson file. If subclasses want to
+ # add other default parameters that depend on the parameters above,
+ # they can override _merge_hjson and add their parameters at the start
+ # of that.
+ self._merge_hjson(hjson_data)
+
+ # Is this a primary config? If so, we need to load up all the child
+ # configurations at this point. If not, we place ourselves into
+ # self.cfgs and consider ourselves a sort of "degenerate primary
+ # configuration".
+ self.is_primary_cfg = 'use_cfgs' in hjson_data
+
+ if not self.is_primary_cfg:
+ self.cfgs.append(self)
+ else:
+ for entry in self.use_cfgs:
+ self._load_child_cfg(entry, mk_config)
+
+ # Process overrides before substituting wildcards
+ self._process_overrides()
+
+ # Expand wildcards. If subclasses need to mess around with parameters
+ # after merging the hjson but before expansion, they can override
+ # _expand and add the code at the start.
+ self._expand()
+
+ if self.rel_path == "":
+ self.rel_path = os.path.dirname(self.flow_cfg_file).replace(
+ self.proj_root + '/', '')
+
+ # Run any final checks
+ self._post_init()
+
+ def _merge_hjson(self, hjson_data):
+ '''Take hjson data and merge it into self.__dict__
+
+ Subclasses that need to do something just before the merge should
+ override this method and call super()._merge_hjson(..) at the end.
+
+ '''
+ for key, value in hjson_data.items():
+ set_target_attribute(self.flow_cfg_file,
+ self.__dict__,
+ key,
+ value)
+
+ def _expand(self):
+ '''Called to expand wildcards after merging hjson
+
+ Subclasses can override this to do something just before expansion.
+
+ '''
+ # If this is a primary configuration, it doesn't matter if we don't
+ # manage to expand everything.
+ partial = self.is_primary_cfg
+
+ self.__dict__ = find_and_substitute_wildcards(self.__dict__,
+ self.__dict__,
+ self.ignored_wildcards,
+ ignore_error=partial)
+
+ def _post_init(self):
# Run some post init checks
if not self.is_primary_cfg:
# Check if self.cfgs is a list of exactly 1 item (self)
@@ -103,11 +175,27 @@
log.error("Parse error!\n%s", self.cfgs)
sys.exit(1)
- def create_instance(self, flow_cfg_file):
+ def create_instance(self, mk_config, flow_cfg_file):
'''Create a new instance of this class for the given config file.
+ mk_config is a factory method (passed explicitly to avoid a circular
+ dependency between this file and CfgFactory.py).
+
'''
- return type(self)(flow_cfg_file, self.proj_root, self.args)
+ new_instance = mk_config(flow_cfg_file)
+
+ # Sanity check to make sure the new object is the same class as us: we
+ # don't yet support heterogeneous primary configurations.
+ if type(self) is not type(new_instance):
+ log.error("{}: Loading child configuration at {!r}, but the "
+ "resulting flow types don't match: ({} vs. {})."
+ .format(self.flow_cfg_file,
+ flow_cfg_file,
+ type(self).__name__,
+ type(new_instance).__name__))
+ sys.exit(1)
+
+ return new_instance
def kill(self):
'''kill running processes and jobs gracefully
@@ -115,187 +203,38 @@
for item in self.deploy:
item.kill()
- def _parse_cfg(self, path, is_entry_point):
- '''Load an hjson config file at path and update self accordingly.
+ def _load_child_cfg(self, entry, mk_config):
+ '''Load a child configuration for a primary cfg'''
+ if type(entry) is str:
+ # Treat this as a file entry. Substitute wildcards in cfg_file
+ # files since we need to process them right away.
+ cfg_file = subst_wildcards(entry,
+ self.__dict__,
+ ignore_error=True)
+ self.cfgs.append(self.create_instance(mk_config, cfg_file))
- If is_entry_point is true, this is the top-level configuration file, so
- it's possible that this is a primary config.
+ elif type(entry) is dict:
+ # Treat this as a cfg expanded in-line
+ temp_cfg_file = self._conv_inline_cfg_to_hjson(entry)
+ if not temp_cfg_file:
+ return
+ self.cfgs.append(self.create_instance(mk_config, temp_cfg_file))
- '''
- hjson_dict = parse_hjson(path)
+ # Delete the temp_cfg_file once the instance is created
+ try:
+ log.log(VERBOSE, "Deleting temp cfg file:\n%s",
+ temp_cfg_file)
+ os.system("/bin/rm -rf " + temp_cfg_file)
+ except IOError:
+ log.error("Failed to remove temp cfg file:\n%s",
+ temp_cfg_file)
- # Check if this is the primary cfg, if this is the entry point cfg file
- if is_entry_point:
- self.is_primary_cfg = self.check_if_primary_cfg(hjson_dict)
-
- # If not a primary cfg, then register self with self.cfgs
- if self.is_primary_cfg is False:
- self.cfgs.append(self)
-
- # Resolve the raw hjson dict to build this object
- self.resolve_hjson_raw(path, hjson_dict)
-
- def _parse_flow_cfg(self, path):
- '''Parse the flow's hjson configuration.
-
- This is a private API which should be called by the __init__ method of
- each subclass.
-
- '''
- self._parse_cfg(path, True)
- if self.rel_path == "":
- self.rel_path = os.path.dirname(self.flow_cfg_file).replace(
- self.proj_root + '/', '')
-
- def check_if_primary_cfg(self, hjson_dict):
- # This is a primary cfg only if it has a single key called "use_cfgs"
- # which contains a list of actual flow cfgs.
- hjson_cfg_dict_keys = hjson_dict.keys()
- return ("use_cfgs" in hjson_cfg_dict_keys and type(hjson_dict["use_cfgs"]) is list)
-
- def _set_attribute(self, path, key, dict_val):
- '''Set an attribute from an hjson file
-
- The path argument is the path for the hjson file that we're reading.
-
- '''
- # Is this value overridden on the command line? If so, use the override
- # instead.
- args_val = None
- if key in _CMDLINE_FIELDS:
- args_val = getattr(self.args, key, None)
- override_msg = ''
- if args_val is not None:
- dict_val = args_val
- override_msg = ' from command-line override'
-
- self_val = getattr(self, key, None)
- if self_val is None:
- # A new attribute (or the old value was None, in which case it's
- # just a placeholder and needs writing). Set it and return.
- setattr(self, key, dict_val)
- return
-
- # This is already an attribute. Firstly, we need to make sure the types
- # are compatible.
- if type(dict_val) != type(self_val):
- log.error("Conflicting types for key {!r} when loading {!r}. "
- "Cannot override value {!r} with {!r} (which is of "
- "type {}{})."
- .format(key, path, self_val, dict_val,
- type(dict_val).__name__, override_msg))
+ else:
+ log.error(
+ "Type of entry \"%s\" in the \"use_cfgs\" key is invalid: %s",
+ entry, str(type(entry)))
sys.exit(1)
- # Looks like the types are compatible. If they are lists, concatenate
- # them.
- if isinstance(self_val, list):
- setattr(self, key, self_val + dict_val)
- return
-
- # Otherwise, check whether this is a type we know how to deal with.
- scalar_types = {str: [""], int: [0, -1], bool: [False]}
-
- defaults = scalar_types.get(type(dict_val))
- if defaults is None:
- log.error("When loading {!r} and setting key {!r}, found a value "
- "of {!r}{} with unsupported type {}."
- .format(path, key, dict_val,
- override_msg, type(dict_val).__name__))
- sys.exit(1)
-
- # If the values are equal, there's nothing more to do
- if self_val == dict_val:
- return
-
- old_is_default = self_val in defaults
- new_is_default = dict_val in defaults
-
- # Similarly, if new value looks like a default, ignore it (regardless
- # of whether the current value looks like a default).
- if new_is_default:
- return
-
- # If the existing value looks like a default and the new value doesn't,
- # take the new value.
- if old_is_default:
- setattr(self, key, dict_val)
- return
-
- # Neither value looks like a default. Raise an error.
- log.error("When loading {!r}, key {!r} is given a value of "
- "{!r}{}, but the key is already set to {!r}."
- .format(path, key, dict_val, override_msg, self_val))
- sys.exit(1)
-
- def resolve_hjson_raw(self, path, hjson_dict):
- import_cfgs = []
- use_cfgs = []
- for key, dict_val in hjson_dict.items():
- # If key is 'import_cfgs' then add to the list of cfgs to process
- if key == 'import_cfgs':
- import_cfgs.extend(dict_val)
- continue
-
- # If the key is 'use_cfgs', we're only allowed to take effect for a
- # primary config list. If we are in a primary config list, add it.
- if key == 'use_cfgs':
- if not self.is_primary_cfg:
- log.error("Key 'use_cfgs' encountered in the non-primary "
- "cfg file list {!r}."
- .format(path))
- sys.exit(1)
-
- use_cfgs.extend(dict_val)
- continue
-
- # Otherwise, set an attribute on self.
- self._set_attribute(path, key, dict_val)
-
- # Parse imported cfgs
- for cfg_file in import_cfgs:
- if cfg_file not in self.imported_cfg_files:
- self.imported_cfg_files.append(cfg_file)
- # Substitute wildcards in cfg_file files since we need to process
- # them right away.
- cfg_file = subst_wildcards(cfg_file, self.__dict__)
- self._parse_cfg(cfg_file, False)
- else:
- log.error("Cfg file \"%s\" has already been parsed", cfg_file)
-
- # Parse primary cfg files
- if self.is_primary_cfg:
- for entry in use_cfgs:
- if type(entry) is str:
- # Treat this as a file entry
- # Substitute wildcards in cfg_file files since we need to process
- # them right away.
- cfg_file = subst_wildcards(entry,
- self.__dict__,
- ignore_error=True)
- self.cfgs.append(self.create_instance(cfg_file))
-
- elif type(entry) is dict:
- # Treat this as a cfg expanded in-line
- temp_cfg_file = self._conv_inline_cfg_to_hjson(entry)
- if not temp_cfg_file:
- continue
- self.cfgs.append(self.create_instance(temp_cfg_file))
-
- # Delete the temp_cfg_file once the instance is created
- try:
- log.log(VERBOSE, "Deleting temp cfg file:\n%s",
- temp_cfg_file)
- os.system("/bin/rm -rf " + temp_cfg_file)
- except IOError:
- log.error("Failed to remove temp cfg file:\n%s",
- temp_cfg_file)
-
- else:
- log.error(
- "Type of entry \"%s\" in the \"use_cfgs\" key is invalid: %s",
- entry, str(type(entry)))
- sys.exit(1)
-
def _conv_inline_cfg_to_hjson(self, idict):
'''Dump a temp hjson file in the scratch space from input dict.
This method is to be called only by a primary cfg'''
diff --git a/util/dvsim/FpvCfg.py b/util/dvsim/FpvCfg.py
index ba0143e..1add68a 100644
--- a/util/dvsim/FpvCfg.py
+++ b/util/dvsim/FpvCfg.py
@@ -15,14 +15,14 @@
class FpvCfg(OneShotCfg):
"""Derivative class for FPV purposes.
"""
- def __init__(self, flow_cfg_file, proj_root, args):
- super().__init__(flow_cfg_file, proj_root, args)
+
+ flow = 'fpv'
+
+ def __init__(self, flow_cfg_file, hjson_data, args, mk_config):
+ super().__init__(flow_cfg_file, hjson_data, args, mk_config)
self.header = ["name", "errors", "warnings", "proven", "cex", "undetermined",
"covered", "unreachable", "pass_rate", "cov_rate"]
self.summary_header = ["name", "pass_rate", "stimuli_cov", "coi_cov", "prove_cov"]
-
- def __post_init__(self):
- super().__post_init__()
self.results_title = self.name.upper() + " FPV Results"
def parse_dict_to_str(self, input_dict, excl_keys = []):
diff --git a/util/dvsim/LintCfg.py b/util/dvsim/LintCfg.py
index 75cf485..7854bb4 100644
--- a/util/dvsim/LintCfg.py
+++ b/util/dvsim/LintCfg.py
@@ -18,13 +18,13 @@
class LintCfg(OneShotCfg):
"""Derivative class for linting purposes.
"""
- def __init__(self, flow_cfg_file, proj_root, args):
+
+ flow = 'lint'
+
+ def __init__(self, flow_cfg_file, hjson_data, args, mk_config):
# This is a lint-specific attribute
self.is_style_lint = ""
- super().__init__(flow_cfg_file, proj_root, args)
-
- def __post_init__(self):
- super().__post_init__()
+ super().__init__(flow_cfg_file, hjson_data, args, mk_config)
# Convert to boolean
if self.is_style_lint == "True":
@@ -53,7 +53,6 @@
results_str += "### Branch: " + self.branch + "\n"
results_str += "\n"
-
header = [
"Name", "Tool Warnings", "Tool Errors", "Lint Warnings",
"Lint Errors"
@@ -186,7 +185,6 @@
("Lint Warnings", "lint_warnings"),
("Lint Errors", "lint_errors")]
-
# Lint fails if any warning or error message has occurred
self.errors_seen = False
for _, key in hdr_key_pairs:
diff --git a/util/dvsim/OneShotCfg.py b/util/dvsim/OneShotCfg.py
index 5687729..1612734 100644
--- a/util/dvsim/OneShotCfg.py
+++ b/util/dvsim/OneShotCfg.py
@@ -13,18 +13,17 @@
from Deploy import CompileOneShot
from FlowCfg import FlowCfg
from Modes import BuildModes, Modes
-from utils import VERBOSE, find_and_substitute_wildcards
class OneShotCfg(FlowCfg):
"""Simple one-shot build flow for non-simulation targets like
linting, synthesis and FPV.
"""
- def __init__(self, flow_cfg_file, proj_root, args):
- super().__init__(flow_cfg_file, proj_root, args)
- assert args.tool is not None
+ ignored_wildcards = (FlowCfg.ignored_wildcards +
+ ['build_mode', 'index', 'test'])
+ def __init__(self, flow_cfg_file, hjson_data, args, mk_config):
# Options set from command line
self.tool = args.tool
self.verbose = args.verbose
@@ -75,22 +74,18 @@
self.build_list = []
self.deploy = []
self.cov = args.cov
- # Parse the cfg_file file tree
- self._parse_flow_cfg(flow_cfg_file)
+ super().__init__(flow_cfg_file, hjson_data, args, mk_config)
+
+ def _merge_hjson(self, hjson_data):
# If build_unique is set, then add current timestamp to uniquify it
if self.build_unique:
self.build_dir += "_" + self.timestamp
- # Process overrides before substituting the wildcards.
- self._process_overrides()
+ super()._merge_hjson(hjson_data)
- # Make substitutions, while ignoring the following wildcards
- # TODO: Find a way to set these in sim cfg instead
- ignored_wildcards = ["build_mode", "index", "test"]
- self.__dict__ = find_and_substitute_wildcards(self.__dict__,
- self.__dict__,
- ignored_wildcards)
+ def _expand(self):
+ super()._expand()
# Stuff below only pertains to individual cfg (not primary cfg).
if not self.is_primary_cfg:
@@ -113,13 +108,6 @@
# tests and regressions, only if not a primary cfg obj
self._create_objects()
- # Post init checks
- self.__post_init__()
-
- def __post_init__(self):
- # Run some post init checks
- super().__post_init__()
-
# Purge the output directories. This operates on self.
def _purge(self):
if self.scratch_path:
diff --git a/util/dvsim/SimCfg.py b/util/dvsim/SimCfg.py
index 575a881..bad908d 100644
--- a/util/dvsim/SimCfg.py
+++ b/util/dvsim/SimCfg.py
@@ -17,7 +17,7 @@
from FlowCfg import FlowCfg
from Modes import BuildModes, Modes, Regressions, RunModes, Tests
from tabulate import tabulate
-from utils import VERBOSE, find_and_substitute_wildcards
+from utils import VERBOSE
from testplanner import class_defs, testplan_utils
@@ -48,8 +48,16 @@
A simulation configuration class holds key information required for building a DV
regression framework.
"""
- def __init__(self, flow_cfg_file, proj_root, args):
- super().__init__(flow_cfg_file, proj_root, args)
+
+ flow = 'sim'
+
+ # TODO: Find a way to set these in sim cfg instead
+ ignored_wildcards = [
+ "build_mode", "index", "test", "seed", "uvm_test", "uvm_test_seq",
+ "cov_db_dirs", "sw_images", "sw_build_device"
+ ]
+
+ def __init__(self, flow_cfg_file, hjson_data, args, mk_config):
# Options set from command line
self.tool = args.tool
self.build_opts = []
@@ -73,10 +81,7 @@
self.profile = args.profile or '(cfg uses profile without --profile)'
self.xprop_off = args.xprop_off
self.no_rerun = args.no_rerun
- # Single-character verbosity setting (n, l, m, h, d). args.verbosity
- # might be None, in which case we'll pick up a default value from
- # configuration files.
- self.verbosity = args.verbosity
+ self.verbosity = None # set in _expand
self.verbose = args.verbose
self.dry_run = args.dry_run
self.map_full_testplan = args.map_full_testplan
@@ -139,9 +144,9 @@
# Maintain an array of those in cov_deploys.
self.cov_deploys = []
- # Parse the cfg_file file tree
- self._parse_flow_cfg(flow_cfg_file)
+ super().__init__(flow_cfg_file, hjson_data, args, mk_config)
+ def _expand(self):
# Choose a wave format now. Note that this has to happen after parsing
# the configuration format because our choice might depend on the
# chosen tool.
@@ -151,19 +156,15 @@
if self.build_unique:
self.build_dir += "_" + self.timestamp
- # Process overrides before substituting the wildcards.
- self._process_overrides()
+ # If the user specified a verbosity on the command line then
+ # self.args.verbosity will be n, l, m, h or d. Set self.verbosity now.
+ # We will actually have loaded some other verbosity level from the
+ # config file, but that won't have any effect until expansion so we can
+ # safely switch it out now.
+ if self.args.verbosity is not None:
+ self.verbosity = self.args.verbosity
- # Make substitutions, while ignoring the following wildcards
- # TODO: Find a way to set these in sim cfg instead
- ignored_wildcards = [
- "build_mode", "index", "test", "seed", "uvm_test", "uvm_test_seq",
- "cov_db_dirs", "sw_images", "sw_build_device"
- ]
- self.__dict__ = find_and_substitute_wildcards(self.__dict__,
- self.__dict__,
- ignored_wildcards,
- self.is_primary_cfg)
+ super()._expand()
# Set the title for simulation results.
self.results_title = self.name.upper() + " Simulation Results"
@@ -202,13 +203,6 @@
# tests and regressions, only if not a primary cfg obj
self._create_objects()
- # Post init checks
- self.__post_init__()
-
- def __post_init__(self):
- # Run some post init checks
- super().__post_init__()
-
def _resolve_waves(self):
'''Choose and return a wave format, if waves are enabled.
diff --git a/util/dvsim/SynCfg.py b/util/dvsim/SynCfg.py
index f0bf107..9a11841 100644
--- a/util/dvsim/SynCfg.py
+++ b/util/dvsim/SynCfg.py
@@ -18,11 +18,11 @@
class SynCfg(OneShotCfg):
"""Derivative class for synthesis purposes.
"""
- def __init__(self, flow_cfg_file, proj_root, args):
- super().__init__(flow_cfg_file, proj_root, args)
- def __post_init__(self):
- super().__post_init__()
+ flow = 'syn'
+
+ def __init__(self, flow_cfg_file, hjson_data, args, mk_config):
+ super().__init__(flow_cfg_file, hjson_data, args, mk_config)
# Set the title for synthesis results.
self.results_title = self.name.upper() + " Synthesis Results"
diff --git a/util/dvsim/dvsim.py b/util/dvsim/dvsim.py
index 6c3dd38..801e427 100755
--- a/util/dvsim/dvsim.py
+++ b/util/dvsim/dvsim.py
@@ -28,11 +28,8 @@
import textwrap
from signal import SIGINT, signal
+from CfgFactory import make_cfg
import Deploy
-import FpvCfg
-import LintCfg
-import SimCfg
-import SynCfg
import utils
# TODO: add dvsim_cfg.hjson to retrieve this info
@@ -136,26 +133,6 @@
return branch
-def make_config(args, proj_root):
- '''A factory method for FlowCfg objects'''
-
- # Look up the tool in a dictionary, defaulting to SimCfg.
- #
- # If --tool was not passed (so args.tool is None), we have to figure out
- # the tool by reading the config file. At the moment, this forces a
- # simulation target (TODO?)
- factories = {
- 'ascentlint': LintCfg.LintCfg,
- 'veriblelint': LintCfg.LintCfg,
- 'verilator': LintCfg.LintCfg,
- 'dc': SynCfg.SynCfg,
- 'jaspergold': FpvCfg.FpvCfg
- }
-
- factory = factories.get(args.tool, SimCfg.SimCfg)
- return factory(args.cfg, proj_root, args)
-
-
# Get the project root directory path - this is used to construct the full paths
def get_proj_root():
cmd = ["git", "rev-parse", "--show-toplevel"]
@@ -590,7 +567,7 @@
proj_root = get_proj_root()
global cfg
- cfg = make_config(args, proj_root)
+ cfg = make_cfg(args.cfg, args, proj_root)
# Handle Ctrl-C exit.
signal(SIGINT, sigint_handler)