#!/usr/bin/env python3 # Copyright lowRISC contributors. # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 import argparse import collections.abc import os import shlex import sys import yaml _DEFAULT_CONFIG_FILE = 'ibex_configs.yaml' class ConfigException(Exception): pass def _verify_config(name, config_dict): """Checks a config_dict matches expectations. A config_dict is the dictionary mapping parameters to values for a particular config, it must obey the following rules: - It's a mapping object e.g. OrderedDict - Its values can only be strings, integers or booleans Args: name: The name of the config being checked (used to form useful error messages) config_dict: The config_dict to check, must be a mapping object Returns: Nothing, an exception is thrown if an issue is found Raises: ConfigException: An issue was found with config_dict """ if not isinstance(config_dict, collections.abc.Mapping): raise ConfigException('Config ' + name + ' must have dictionary giving parameters') for k, v in config_dict.items(): if isinstance(v, int): continue if isinstance(v, str): continue if isinstance(v, bool): continue raise ConfigException('Parameter ' + k + ' for config ' + name + ' must be string, int or bool got ' + str(type(v))) def _verify_config_parameters(config_dicts): """Verifies all parameters across config_dicts match expectations. Each configuration must obey the following fules: - Each config has the same set of parameters specified Args: config_dicts: A dictionary of configurations, maps from configuration name to a configuration (itself a dictionary) Returns: Nothing, an exception is thrown if an issue is found Raises: ConfigException: An issue was found with config_dicts """ parameters = set() first = True for name, config_dict in config_dicts.items(): parameters_this_config = set() for parameter, value in config_dict.items(): if first: parameters.add(parameter) parameters_this_config.add(parameter) if first: first = False else: parameter_difference = parameters ^ parameters_this_config if parameter_difference: raise ConfigException('Config ' + name + ' has differing parameters ' + ','.join(parameter_difference)) def get_config_dicts(config_file): """Extracts a dictionary of configuration dictionaries from a file object Given a file object parses YAML from it to obtain a dictionary of configurations Args: config_file: A file object for a file containing the YAML configuration file Returns: A dictionary of configurations, maps from a configuration name to a configuration (itself a dictionary mapping parameters to values) Raises: ConfigException: An issue was found with the configuration file """ try: config_yaml = yaml.load(config_file, Loader=yaml.SafeLoader) except yaml.YAMLError as e: raise ConfigException('Could not decode yaml:\n' + str(e)) for k, v in config_yaml.items(): _verify_config(k, v) _verify_config_parameters(config_yaml) return config_yaml class FusesocOpts: def setup_args(self, arg_subparser): output_argparser = arg_subparser.add_parser( 'fusesoc_opts', help=('Outputs options for fusesoc')) output_argparser.set_defaults(output_fn=self.output) def output(self, config_dict, args): fusesoc_cmd = [] for parameter, value in config_dict.items(): if isinstance(value, bool): # For fusesoc boolean parameters are set to true if given on the # command line otherwise false. It doesn't support an explicit # --param=True style if value: fusesoc_cmd.append(shlex.quote('--' + parameter)) else: fusesoc_cmd.append( shlex.quote('--' + parameter + '=' + str(value))) return ' '.join(fusesoc_cmd) class SimOpts: def __init__(self, cmd_name, description, param_set_fn, define_set_fn, hierarchy_sep): self.cmd_name = cmd_name self.description = description self.param_set_fn = param_set_fn self.define_set_fn = define_set_fn self.hierarchy_sep = hierarchy_sep def setup_args(self, arg_subparser): output_argparser = arg_subparser.add_parser( self.cmd_name, help=('Outputs options for {0}'.format(self.description))) output_argparser.add_argument( '--ins_hier_path', help=('Hierarchical path to the instance to set ' 'configuration parameters on'), default='') output_argparser.add_argument( '--string_define_prefix', help=('Prefix to add to defines that are used to ' 'pass string parameters'), default='') output_argparser.set_defaults(output_fn=self.output) def output(self, config_dict, args): if (args.ins_hier_path != ''): ins_hier_path = args.ins_hier_path + self.hierarchy_sep else: ins_hier_path = '' sim_opts = [] for parameter, value in config_dict.items(): if isinstance(value, str): parameter_define = args.string_define_prefix + parameter define_set_str = self.define_set_fn(parameter_define, value) if define_set_str: sim_opts.append(shlex.quote(define_set_str)) else: if isinstance(value, bool): val_str = '1' if value else '0' else: val_str = str(value) param_set_str = self.param_set_fn(ins_hier_path + parameter, val_str) if param_set_str: sim_opts.append(shlex.quote(param_set_str)) return ' '.join(sim_opts) def get_config_file_location(): """Returns the location of the config file Default is _DEFAULT_CONFIG_FILE and the IBEX_CONFIG_FILE environment variable overrides the default""" return os.environ.get('IBEX_CONFIG_FILE', _DEFAULT_CONFIG_FILE) def main(): outputters = [ FusesocOpts(), SimOpts('vcs_opts', 'VCS compile', lambda p, v: '-pvalue+' + p + '=' + v, lambda d, v: '+define+' + d + '=' + v, '.'), SimOpts('riviera_sim_opts', 'Riviera simulate', lambda p, v: '-g/' + p + '=' + v, lambda d, v: None, '/'), SimOpts('riviera_compile_opts', 'Riviera compile', lambda p, v: None, lambda d, v: '+define+' + d + '=' + v, '/'), SimOpts('questa_sim_opts', 'Questa simulate', lambda p, v: '-g/' + p + '=' + v, lambda d, v: None, '/'), SimOpts('questa_compile_opts', 'Questa compile', lambda p, v: None, lambda d, v: '+define+' + d + '=' + v, '/'), SimOpts('xlm_opts', 'Xcelium compile', lambda p, v: '-defparam ' + p + '=' + v, lambda d, v: '-define ' + d + '=' + v, '.'), SimOpts('dsim_compile_opts', 'DSim compile', lambda p, v: '+define+' + p + '=' + v, lambda d, v: None, '/'), ] argparser = argparse.ArgumentParser(description=( 'Outputs Ibex configuration parameters for a named config in a number ' 'of formats. If not specified on the command line the config will be ' 'read from {0}. This default can be overridden by setting the ' 'IBEX_CONFIG_FILE environment variable. Some output types support ' 'arguments to see help for them pass a config_name and an output type ' 'followed by --help').format(get_config_file_location())) argparser.add_argument('config_name', help=('The name of the Ibex ' 'configuration to output')) argparser.add_argument('--config_filename', help='Config file to read', default=get_config_file_location()) arg_subparser = argparser.add_subparsers( help='Format to output the configuration parameters in', dest='output_fn', metavar='output_type') for outputter in outputters: outputter.setup_args(arg_subparser) args = argparser.parse_args() if args.output_fn is None: print('ERROR: No output format specified.') sys.exit(1) try: config_file = open(args.config_filename) config_dicts = get_config_dicts(config_file) if args.config_name not in config_dicts: print('ERROR: configuration', args.config_name, 'not found in', args.config_filename, file=sys.stderr) sys.exit(1) print(args.output_fn(config_dicts[args.config_name], args)) except ConfigException as ce: print('ERROR: failure to process configuration from', args.config_filename, ce, file=sys.stderr) sys.exit(1) except FileNotFoundError: print('ERROR: could not find configuration file', args.config_filename, file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()