| #!/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 os |
| import shlex |
| import sys |
| |
| import yaml |
| |
| _DEFAULT_CONFIG_FILE = 'ibex_configs.yaml' |
| |
| |
| class ConfigException(Exception): |
| pass |
| |
| |
| class Config: |
| '''An object representing an Ibex configuration''' |
| known_fields = [ |
| ('RV32E', bool), |
| ('RV32M', str), |
| ('RV32B', str), |
| ('RegFile', str), |
| ('BranchTargetALU', bool), |
| ('WritebackStage', bool), |
| ('ICache', bool), |
| ('ICacheECC', bool), |
| ('ICacheScramble', bool), |
| ('BranchPredictor', bool), |
| ('DbgTriggerEn', bool), |
| ('SecureIbex', bool), |
| ('PMPEnable', bool), |
| ('PMPGranularity', int), |
| ('PMPNumRegions', int), |
| ('MHPMCounterNum', int), |
| ('MHPMCounterWidth', int) |
| ] |
| |
| def __init__(self, yml): |
| if not isinstance(yml, dict): |
| raise ValueError('Configuration object is not a dict') |
| |
| yaml_keys = set(yml.keys()) |
| known_keys = {fld for (fld, typ) in Config.known_fields} |
| |
| extra_keys = yaml_keys - known_keys |
| if extra_keys: |
| raise ValueError(f'Configuration object has ' |
| f'unknown keys: {extra_keys}') |
| |
| missing_keys = known_keys - yaml_keys |
| if missing_keys: |
| raise ValueError(f'Configuration object has ' |
| f'missing keys: {extra_keys}') |
| |
| self.params = yml |
| |
| self.rv32e = Config.read_bool('RV32E', yml) |
| self.rv32m = Config.read_str('RV32M', yml) |
| self.rv32b = Config.read_str('RV32B', yml) |
| self.reg_file = Config.read_str('RegFile', yml) |
| self.branch_target_alu = Config.read_bool('BranchTargetALU', yml) |
| self.writeback_stage = Config.read_bool('WritebackStage', yml) |
| self.icache = Config.read_bool('ICache', yml) |
| self.icache_ecc = Config.read_bool('ICacheECC', yml) |
| self.icache_scramble = Config.read_bool('ICacheScramble', yml) |
| self.branch_predictor = Config.read_bool('BranchPredictor', yml) |
| self.dbg_trigger_en = Config.read_bool('DbgTriggerEn', yml) |
| self.secure_ibex = Config.read_bool('SecureIbex', yml) |
| self.pmp_enable = Config.read_bool('PMPEnable', yml) |
| self.pmp_granularity = Config.read_int('PMPGranularity', yml) |
| self.pmp_num_regions = Config.read_int('PMPNumRegions', yml) |
| self.mhpm_counter_num = Config.read_int('MHPMCounterNum', yml) |
| self.mhpm_counter_width = Config.read_int('MHPMCounterWidth', yml) |
| |
| @staticmethod |
| def read_bool(fld, yml): |
| val = yml[fld] |
| if isinstance(val, bool): |
| return val |
| if isinstance(val, int): |
| if 0 <= val <= 1: |
| return val != 0 |
| |
| raise ValueError(f'{fld} value is {val}, which is out of ' |
| 'range for a boolean type.') |
| raise ValueError(f'{fld} value is {val!r}, but we expected a bool.') |
| |
| @staticmethod |
| def read_int(fld, yml): |
| val = yml[fld] |
| if isinstance(val, int): |
| return val |
| raise ValueError(f'{fld} value is {val!r}, but we expected an int.') |
| |
| @staticmethod |
| def read_str(fld, yml): |
| val = yml[fld] |
| if isinstance(val, str): |
| return val |
| raise ValueError(f'{fld} value is {val!r}, but we expected a string.') |
| |
| |
| class Configs: |
| def __init__(self, yml): |
| if not isinstance(yml, dict): |
| raise ValueError('Configurations dictionary is not a dict') |
| |
| self.configs = {} |
| for cfg_name, cfg_yaml in yml.items(): |
| try: |
| self.configs[cfg_name] = Config(cfg_yaml) |
| except ValueError as err: |
| raise ValueError(f'Error when reading ' |
| f'{cfg_name!r} config: {err}') from None |
| |
| |
| 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, args): |
| fusesoc_cmd = [] |
| for fld, typ in Config.known_fields: |
| val = config.params[fld] |
| fusesoc_cmd.append(shlex.quote(f'--{fld}={val}')) |
| |
| return ' '.join(fusesoc_cmd) |
| |
| class QueryOpts: |
| def setup_args(self, arg_subparser): |
| output_argparser = arg_subparser.add_parser( |
| 'query_fields', help=('Query config fields')) |
| output_argparser.add_argument( |
| 'fields', type=str, nargs='+', |
| help='Which fields to query the value of') |
| |
| output_argparser.set_defaults(output_fn=self.output) |
| |
| def output(self, config, args): |
| query_result = [] |
| for fld in args.fields: |
| if fld in config.params: |
| val = config.params[fld] |
| query_result.append(f'{fld}={val}') |
| else: |
| query_result.append(f'{fld} not found in config') |
| |
| return '\n'.join(query_result) |
| |
| 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, args): |
| if (args.ins_hier_path != ''): |
| ins_hier_path = args.ins_hier_path + self.hierarchy_sep |
| else: |
| ins_hier_path = '' |
| |
| sim_opts = [] |
| |
| for fld, typ in Config.known_fields: |
| val = config.params[fld] |
| |
| if typ is str: |
| parameter_define = args.string_define_prefix + fld |
| define_opts = self.define_set_fn(parameter_define, val) |
| sim_opts += [shlex.quote(arg) for arg in define_opts] |
| else: |
| assert typ in [bool, int] |
| |
| # Explicitly convert to 0/1 (handling genuine booleans) |
| val_as_int = int(val) |
| |
| full_param = ins_hier_path + fld |
| param_opts = self.param_set_fn(full_param, str(val_as_int)) |
| sim_opts += [shlex.quote(arg) for arg in param_opts] |
| |
| 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 parse_config(config_name, config_filename): |
| """Parses the selected config file and returns selected config information. |
| |
| Arguments: |
| |
| config_name: Name of the chosen Ibex core config |
| |
| config_filename: Name of the configuration filename to be parsed |
| |
| Returns: the chosen Ibex config as a Config object. |
| |
| Raises an exception if there is an error loading or parsing the YAML, or if |
| the YAML doesn't define a configuration with the requested name. |
| |
| """ |
| with open(config_filename) as config_file: |
| try: |
| yml = yaml.load(config_file, Loader=yaml.SafeLoader) |
| except yaml.YAMLError as err: |
| raise ConfigException(f'Could not decode yaml: {err}') |
| |
| try: |
| configs = Configs(yml) |
| except ValueError as err: |
| raise ConfigException(f'{config_filename!r}: {err}') from None |
| |
| config = configs.configs.get(config_name) |
| if config is None: |
| raise ValueError(f'Configuration {config_name!r} not found ' |
| 'in YAML at {config_filename!r}.') |
| |
| return config |
| |
| |
| def main(): |
| outputters = [ |
| FusesocOpts(), |
| QueryOpts(), |
| 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: [], '/'), |
| SimOpts('riviera_compile_opts', 'Riviera compile', |
| lambda p, v: [], |
| lambda d, v: ['+define+' + d + '=' + v], '/'), |
| SimOpts('questa_sim_opts', 'Questa simulate', |
| lambda p, v: ['-g/' + p + '=' + v], |
| lambda d, v: [], '/'), |
| SimOpts('questa_compile_opts', 'Questa compile', |
| lambda p, v: [], |
| 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: [], '/'), |
| ] |
| |
| 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) |
| |
| parsed_ibex_config = parse_config(args.config_name, args.config_filename) |
| print(args.output_fn(parsed_ibex_config, args)) |
| |
| if __name__ == "__main__": |
| main() |