mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-22 21:07:34 -04:00
Strengthen types in ibex_config.py
This should make it easier for other scripts to use parse_config().
This commit is contained in:
parent
3ad3bd0d71
commit
b63ab3b120
2 changed files with 120 additions and 139 deletions
|
@ -65,7 +65,7 @@ def filter_tests_by_config(cfg: str, test_list: _TestEntries) -> _TestEntries:
|
|||
param_dict = test['rtl_params']
|
||||
assert isinstance(param_dict, dict)
|
||||
for p, p_val in param_dict.items():
|
||||
config_val = config.get(p, None)
|
||||
config_val = config.params.get(p, None)
|
||||
# Throw an error if required RTL parameters in the testlist
|
||||
# have been formatted incorrectly (typos, wrong parameters,
|
||||
# etc)
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import argparse
|
||||
import collections.abc
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
|
@ -19,112 +18,99 @@ class ConfigException(Exception):
|
|||
pass
|
||||
|
||||
|
||||
def _verify_config(name, config_dict):
|
||||
"""Checks a config_dict matches expectations.
|
||||
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),
|
||||
('BranchPredictor', bool),
|
||||
('PMPEnable', bool),
|
||||
('PMPGranularity', int),
|
||||
('PMPNumRegions', int),
|
||||
('SecureIbex', bool),
|
||||
('ICacheScramble', bool)
|
||||
]
|
||||
|
||||
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
|
||||
def __init__(self, yml):
|
||||
if not isinstance(yml, dict):
|
||||
raise ValueError('Configuration object is not a dict')
|
||||
|
||||
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
|
||||
yaml_keys = set(yml.keys())
|
||||
known_keys = {fld for (fld, typ) in Config.known_fields}
|
||||
|
||||
Returns:
|
||||
Nothing, an exception is thrown if an issue is found
|
||||
extra_keys = yaml_keys - known_keys
|
||||
if extra_keys:
|
||||
raise ValueError(f'Configuration object has '
|
||||
f'unknown keys: {extra_keys}')
|
||||
|
||||
Raises:
|
||||
ConfigException: An issue was found with config_dict
|
||||
"""
|
||||
missing_keys = known_keys - yaml_keys
|
||||
if missing_keys:
|
||||
raise ValueError(f'Configuration object has '
|
||||
f'missing keys: {extra_keys}')
|
||||
|
||||
if not isinstance(config_dict, collections.abc.Mapping):
|
||||
raise ConfigException('Config ' + name +
|
||||
' must have dictionary giving parameters')
|
||||
self.params = yml
|
||||
|
||||
for k, v in config_dict.items():
|
||||
if isinstance(v, int):
|
||||
continue
|
||||
if isinstance(v, str):
|
||||
continue
|
||||
if isinstance(v, bool):
|
||||
continue
|
||||
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.branch_predictor = Config.read_bool('BranchPredictor', 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.secure_ibex = Config.read_bool('SecureIbex', yml)
|
||||
self.icache_scramble = Config.read_bool('ICacheScramble', yml)
|
||||
|
||||
raise ConfigException('Parameter ' + k + ' for config ' + name +
|
||||
' must be string, int or bool got ' +
|
||||
str(type(v)))
|
||||
@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.')
|
||||
|
||||
|
||||
def _verify_config_parameters(config_dicts):
|
||||
"""Verifies all parameters across config_dicts match expectations.
|
||||
class Configs:
|
||||
def __init__(self, yml):
|
||||
if not isinstance(yml, dict):
|
||||
raise ValueError('Configurations dictionary is not a dict')
|
||||
|
||||
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
|
||||
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:
|
||||
|
@ -133,18 +119,11 @@ class FusesocOpts:
|
|||
'fusesoc_opts', help=('Outputs options for fusesoc'))
|
||||
output_argparser.set_defaults(output_fn=self.output)
|
||||
|
||||
def output(self, config_dict, args):
|
||||
def output(self, config, 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)))
|
||||
for fld, typ in Config.known_fields:
|
||||
val = config.params[fld]
|
||||
fusesoc_cmd.append(shlex.quote(f'--{fld}={val}'))
|
||||
|
||||
return ' '.join(fusesoc_cmd)
|
||||
|
||||
|
@ -175,7 +154,7 @@ class SimOpts:
|
|||
default='')
|
||||
output_argparser.set_defaults(output_fn=self.output)
|
||||
|
||||
def output(self, config_dict, args):
|
||||
def output(self, config, args):
|
||||
if (args.ins_hier_path != ''):
|
||||
ins_hier_path = args.ins_hier_path + self.hierarchy_sep
|
||||
else:
|
||||
|
@ -183,19 +162,21 @@ class SimOpts:
|
|||
|
||||
sim_opts = []
|
||||
|
||||
for parameter, value in config_dict.items():
|
||||
if isinstance(value, str):
|
||||
parameter_define = args.string_define_prefix + parameter
|
||||
define_opts = self.define_set_fn(parameter_define, value)
|
||||
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:
|
||||
if isinstance(value, bool):
|
||||
val_str = '1' if value else '0'
|
||||
else:
|
||||
val_str = str(value)
|
||||
assert typ in [bool, int]
|
||||
|
||||
full_param = ins_hier_path + parameter
|
||||
param_opts = self.param_set_fn(full_param, val_str)
|
||||
# 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)
|
||||
|
@ -219,29 +200,29 @@ def parse_config(config_name, config_filename):
|
|||
|
||||
config_filename: Name of the configuration filename to be parsed
|
||||
|
||||
Returns: the chosen Ibex config as a YAML object.
|
||||
Returns: the chosen Ibex config as a Config object.
|
||||
|
||||
Raises a ConfigException if there are any error while parsing the YAML.
|
||||
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.
|
||||
|
||||
Raises a FileNotFoundError if there are errors opening the chosen config file.
|
||||
"""
|
||||
try:
|
||||
config_file = open(config_filename)
|
||||
config_dicts = get_config_dicts(config_file)
|
||||
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}')
|
||||
|
||||
if config_name not in config_dicts:
|
||||
print('ERROR: configuration {!r} not found in {!r}.'.format(
|
||||
config_name, config_filename), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return config_dicts[config_name]
|
||||
except ConfigException as ce:
|
||||
print('ERROR: failure to process configuration from {!r} {!r}.'.format(
|
||||
config_filename, ce), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except FileNotFoundError:
|
||||
print('ERROR: could not find configuration file {!r}.'.format(
|
||||
config_filename), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
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():
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue