| # 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 pprint |
| import sys |
| |
| from utils import VERBOSE |
| |
| |
| class Modes(): |
| """ |
| Abstraction for specifying collection of options called as 'modes'. This is |
| the base class which is extended for run_modes, build_modes, tests and regressions. |
| """ |
| def self_str(self): |
| ''' |
| This is used to construct the string representation of the entire class object. |
| ''' |
| tname = "" |
| if self.type != "": |
| tname = self.type + "_" |
| if self.mname != "": |
| tname += self.mname |
| if log.getLogger().isEnabledFor(VERBOSE): |
| return "\n<---" + tname + ":\n" + pprint.pformat(self.__dict__) + \ |
| "\n--->\n" |
| else: |
| return tname + ":" + self.name |
| |
| def __str__(self): |
| return self.self_str() |
| |
| def __repr__(self): |
| return self.self_str() |
| |
| def __init__(self, mdict): |
| keys = mdict.keys() |
| attrs = self.__dict__.keys() |
| |
| if 'name' not in keys: |
| log.error("Key \"name\" missing in mode %s", mdict) |
| sys.exit(1) |
| |
| if not hasattr(self, "type"): |
| log.fatal("Key \"type\" is missing or invalid") |
| sys.exit(1) |
| |
| if not hasattr(self, "mname"): |
| self.mname = "" |
| |
| for key in keys: |
| if key not in attrs: |
| log.error("Key %s in %s is invalid", key, mdict) |
| sys.exit(1) |
| setattr(self, key, mdict[key]) |
| |
| def get_sub_modes(self): |
| sub_modes = [] |
| if hasattr(self, "en_" + self.type + "_modes"): |
| sub_modes = getattr(self, "en_" + self.type + "_modes") |
| return sub_modes |
| |
| def set_sub_modes(self, sub_modes): |
| setattr(self, "en_" + self.type + "_modes", sub_modes) |
| |
| def merge_mode(self, mode): |
| ''' |
| Merge a new mode with self. |
| Merge sub mode specified with 'en_*_modes with self. |
| ''' |
| |
| sub_modes = self.get_sub_modes() |
| is_sub_mode = mode.name in sub_modes |
| |
| if not mode.name == self.name and not is_sub_mode: |
| return False |
| |
| # only merge the lists; if strs are different, then throw an error |
| attrs = self.__dict__.keys() |
| for attr in attrs: |
| # merge lists together |
| self_attr_val = getattr(self, attr) |
| mode_attr_val = getattr(mode, attr) |
| |
| if type(self_attr_val) is list: |
| self_attr_val.extend(mode_attr_val) |
| setattr(self, attr, self_attr_val) |
| |
| elif not is_sub_mode or attr not in ["name", "mname"]: |
| self.check_conflict(mode.name, attr, mode_attr_val) |
| |
| # Check newly appended sub_modes, remove 'self' and duplicates |
| sub_modes = self.get_sub_modes() |
| |
| if sub_modes != []: |
| new_sub_modes = [] |
| for sub_mode in sub_modes: |
| if self.name != sub_mode and sub_mode not in new_sub_modes: |
| new_sub_modes.append(sub_mode) |
| self.set_sub_modes(new_sub_modes) |
| return True |
| |
| def check_conflict(self, name, attr, mode_attr_val): |
| self_attr_val = getattr(self, attr) |
| if self_attr_val == mode_attr_val: |
| return |
| |
| if mode_attr_val is None: |
| # No override here |
| return |
| |
| default_val = None |
| if type(self_attr_val) is int: |
| default_val = -1 |
| elif type(self_attr_val) is str: |
| default_val = "" |
| |
| if self_attr_val != default_val and mode_attr_val != default_val: |
| log.error( |
| "mode %s cannot be merged into %s due to conflicting %s {%s, %s}", |
| name, self.name, attr, str(self_attr_val), str(mode_attr_val)) |
| sys.exit(1) |
| elif self_attr_val == default_val: |
| self_attr_val = mode_attr_val |
| setattr(self, attr, self_attr_val) |
| |
| @staticmethod |
| def create_modes(ModeType, mdicts): |
| ''' |
| Create modes of type ModeType from a given list of raw dicts |
| Process dependencies. |
| Return a list of modes objects. |
| ''' |
| def merge_sub_modes(mode, parent, objs): |
| # Check if there are modes available to merge |
| sub_modes = mode.get_sub_modes() |
| if sub_modes == []: |
| return |
| |
| # Set parent if it is None. If not, check cyclic dependency |
| if parent is None: |
| parent = mode |
| else: |
| if mode.name == parent.name: |
| log.error("Cyclic dependency when processing mode \"%s\"", |
| mode.name) |
| sys.exit(1) |
| |
| for sub_mode in sub_modes: |
| # Find the sub_mode obj from str |
| found = False |
| for obj in objs: |
| if sub_mode == obj.name: |
| # First recursively merge the sub_modes |
| merge_sub_modes(obj, parent, objs) |
| |
| # Now merge the sub mode with mode |
| mode.merge_mode(obj) |
| found = True |
| break |
| if not found: |
| log.error( |
| "Sub mode \"%s\" added to mode \"%s\" was not found!", |
| sub_mode, mode.name) |
| sys.exit(1) |
| |
| modes_objs = [] |
| # create a default mode if available |
| default_mode = ModeType.get_default_mode() |
| if default_mode is not None: |
| modes_objs.append(default_mode) |
| |
| # Process list of raw dicts that represent the modes |
| # Pass 1: Create unique set of modes by merging modes with the same name |
| for mdict in mdicts: |
| # Create a new item |
| new_mode_merged = False |
| new_mode = ModeType(mdict) |
| for mode in modes_objs: |
| # Merge new one with existing if available |
| if mode.name == new_mode.name: |
| mode.merge_mode(new_mode) |
| new_mode_merged = True |
| break |
| |
| # Add the new mode to the list if not already appended |
| if not new_mode_merged: |
| modes_objs.append(new_mode) |
| ModeType.item_names.append(new_mode.name) |
| |
| # Pass 2: Recursively expand sub modes within parent modes |
| for mode in modes_objs: |
| merge_sub_modes(mode, None, modes_objs) |
| |
| # Return the list of objects |
| return modes_objs |
| |
| @staticmethod |
| def get_default_mode(ModeType): |
| return None |
| |
| @staticmethod |
| def find_mode(mode_name, modes): |
| ''' |
| Given a mode_name in string, go through list of modes and return the mode with |
| the string that matches. Thrown an error and return None if nothing was found. |
| ''' |
| for mode in modes: |
| if mode_name == mode.name: |
| return mode |
| return None |
| |
| @staticmethod |
| def find_and_merge_modes(mode, mode_names, modes, merge_modes=True): |
| ''' |
| ''' |
| found_mode_objs = [] |
| for mode_name in mode_names: |
| sub_mode = Modes.find_mode(mode_name, modes) |
| if sub_mode is not None: |
| found_mode_objs.append(sub_mode) |
| if merge_modes is True: |
| mode.merge_mode(sub_mode) |
| else: |
| log.error("Mode \"%s\" enabled within mode \"%s\" not found!", |
| mode_name, mode.name) |
| sys.exit(1) |
| return found_mode_objs |
| |
| |
| class BuildModes(Modes): |
| """ |
| Build modes. |
| """ |
| |
| # Maintain a list of build_modes str |
| item_names = [] |
| |
| def __init__(self, bdict): |
| self.name = "" |
| self.type = "build" |
| if not hasattr(self, "mname"): |
| self.mname = "mode" |
| self.is_sim_mode = 0 |
| self.build_opts = [] |
| self.run_opts = [] |
| self.en_build_modes = [] |
| |
| super().__init__(bdict) |
| self.en_build_modes = list(set(self.en_build_modes)) |
| |
| @staticmethod |
| def get_default_mode(): |
| return BuildModes({"name": "default"}) |
| |
| |
| class RunModes(Modes): |
| """ |
| Run modes. |
| """ |
| |
| # Maintain a list of run_modes str |
| item_names = [] |
| |
| def __init__(self, rdict): |
| self.name = "" |
| self.type = "run" |
| if not hasattr(self, "mname"): |
| self.mname = "mode" |
| self.reseed = None |
| self.run_opts = [] |
| self.uvm_test = "" |
| self.uvm_test_seq = "" |
| self.build_mode = "" |
| self.en_run_modes = [] |
| self.sw_test = "" |
| self.sw_test_is_prebuilt = "" |
| self.sw_build_device = "" |
| |
| super().__init__(rdict) |
| self.en_run_modes = list(set(self.en_run_modes)) |
| |
| @staticmethod |
| def get_default_mode(): |
| return None |
| |
| |
| class Tests(RunModes): |
| """ |
| Abstraction for tests. The RunModes abstraction can be reused here with a few |
| modifications. |
| """ |
| |
| # Maintain a list of tests str |
| item_names = [] |
| |
| # TODO: This info should be passed via hjson |
| defaults = { |
| "reseed": None, |
| "uvm_test": "", |
| "uvm_test_seq": "", |
| "build_mode": "", |
| "sw_test": "", |
| "sw_test_is_prebuilt": "", |
| "sw_build_device": "", |
| } |
| |
| def __init__(self, tdict): |
| if not hasattr(self, "mname"): |
| self.mname = "test" |
| super().__init__(tdict) |
| |
| @staticmethod |
| def create_tests(tdicts, sim_cfg): |
| ''' |
| Create Tests from a given list of raw dicts. |
| TODO: enhance the raw dict to include file scoped defaults. |
| Process enabled run modes and the set build mode. |
| Return a list of test objects. |
| ''' |
| def get_pruned_en_run_modes(test_en_run_modes, global_en_run_modes): |
| pruned_en_run_modes = [] |
| for test_en_run_mode in test_en_run_modes: |
| if test_en_run_mode not in global_en_run_modes: |
| pruned_en_run_modes.append(test_en_run_mode) |
| return pruned_en_run_modes |
| |
| tests_objs = [] |
| # Pass 1: Create unique set of tests by merging tests with the same name |
| for tdict in tdicts: |
| # Create a new item |
| new_test_merged = False |
| new_test = Tests(tdict) |
| for test in tests_objs: |
| # Merge new one with existing if available |
| if test.name == new_test.name: |
| test.merge_mode(new_test) |
| new_test_merged = True |
| break |
| |
| # Add the new test to the list if not already appended |
| if not new_test_merged: |
| tests_objs.append(new_test) |
| Tests.item_names.append(new_test.name) |
| |
| # Pass 2: Process dependencies |
| build_modes = [] |
| if hasattr(sim_cfg, "build_modes"): |
| build_modes = getattr(sim_cfg, "build_modes") |
| |
| run_modes = [] |
| if hasattr(sim_cfg, "run_modes"): |
| run_modes = getattr(sim_cfg, "run_modes") |
| |
| attrs = Tests.defaults |
| for test_obj in tests_objs: |
| # Unpack run_modes first |
| en_run_modes = get_pruned_en_run_modes(test_obj.en_run_modes, |
| sim_cfg.en_run_modes) |
| Modes.find_and_merge_modes(test_obj, en_run_modes, run_modes) |
| |
| # Find and set the missing attributes from sim_cfg |
| # If not found in sim_cfg either, then throw a warning |
| # TODO: These should be file-scoped |
| for attr in attrs.keys(): |
| # Check if attr value is default |
| val = getattr(test_obj, attr) |
| default_val = attrs[attr] |
| if val == default_val: |
| global_val = None |
| # Check if we can find a default in sim_cfg |
| if hasattr(sim_cfg, attr): |
| global_val = getattr(sim_cfg, attr) |
| |
| if global_val is not None and global_val != default_val: |
| setattr(test_obj, attr, global_val) |
| |
| # Unpack the build mode for this test |
| build_mode_objs = Modes.find_and_merge_modes(test_obj, |
| [test_obj.build_mode], |
| build_modes, |
| merge_modes=False) |
| test_obj.build_mode = build_mode_objs[0] |
| |
| # Error if set build mode is actually a sim mode |
| if test_obj.build_mode.is_sim_mode is True: |
| log.error( |
| "Test \"%s\" uses build_mode %s which is actually a sim mode", |
| test_obj.name, test_obj.build_mode.name) |
| sys.exit(1) |
| |
| # Merge build_mode's run_opts with self |
| test_obj.run_opts.extend(test_obj.build_mode.run_opts) |
| |
| # Return the list of tests |
| return tests_objs |
| |
| @staticmethod |
| def merge_global_opts(tests, global_build_opts, global_run_opts): |
| processed_build_modes = [] |
| for test in tests: |
| if test.build_mode.name not in processed_build_modes: |
| test.build_mode.build_opts.extend(global_build_opts) |
| processed_build_modes.append(test.build_mode.name) |
| test.run_opts.extend(global_run_opts) |
| |
| |
| class Regressions(Modes): |
| """ |
| Abstraction for test sets / regression sets. |
| """ |
| |
| # Maintain a list of tests str |
| item_names = [] |
| |
| # TODO: define __repr__ and __str__ to print list of tests if VERBOSE |
| |
| def __init__(self, regdict): |
| self.name = "" |
| self.type = "" |
| if not hasattr(self, "mname"): |
| self.mname = "regression" |
| self.tests = [] |
| self.reseed = None |
| self.test_names = [] |
| self.excl_tests = [] # TODO: add support for this |
| self.en_sim_modes = [] |
| self.en_run_modes = [] |
| self.build_opts = [] |
| self.run_opts = [] |
| super().__init__(regdict) |
| |
| @staticmethod |
| def create_regressions(regdicts, sim_cfg, tests): |
| ''' |
| Create Test sets from a given list of raw dicts. |
| Return a list of test set objects. |
| ''' |
| |
| regressions_objs = [] |
| # Pass 1: Create unique set of test sets by merging test sets with the same name |
| for regdict in regdicts: |
| # Create a new item |
| new_regression_merged = False |
| new_regression = Regressions(regdict) |
| |
| # Check for name conflicts with tests before merging |
| if new_regression.name in Tests.item_names: |
| log.error("Test names and regression names are required to be unique. " |
| "The regression \"%s\" bears the same name with an existing test. ", |
| new_regression.name) |
| sys.exit(1) |
| |
| for regression in regressions_objs: |
| # Merge new one with existing if available |
| if regression.name == new_regression.name: |
| regression.merge_mode(new_regression) |
| new_regression_merged = True |
| break |
| |
| # Add the new test to the list if not already appended |
| if not new_regression_merged: |
| regressions_objs.append(new_regression) |
| Regressions.item_names.append(new_regression.name) |
| |
| # Pass 2: Process dependencies |
| build_modes = [] |
| if hasattr(sim_cfg, "build_modes"): |
| build_modes = getattr(sim_cfg, "build_modes") |
| |
| run_modes = [] |
| if hasattr(sim_cfg, "run_modes"): |
| run_modes = getattr(sim_cfg, "run_modes") |
| |
| for regression_obj in regressions_objs: |
| # Unpack the sim modes |
| found_sim_mode_objs = Modes.find_and_merge_modes( |
| regression_obj, regression_obj.en_sim_modes, build_modes, |
| False) |
| |
| for sim_mode_obj in found_sim_mode_objs: |
| if sim_mode_obj.is_sim_mode == 0: |
| log.error( |
| "Enabled mode \"%s\" within the regression \"%s\" is not a sim mode", |
| sim_mode_obj.name, regression_obj.name) |
| sys.exit(1) |
| |
| # Check if sim_mode_obj's sub-modes are a part of regressions's |
| # sim modes- if yes, then it will cause duplication of opts |
| # Throw an error and exit. |
| for sim_mode_obj_sub in sim_mode_obj.en_build_modes: |
| if sim_mode_obj_sub in regression_obj.en_sim_modes: |
| log.error("Regression \"%s\" enables sim_modes \"%s\" and \"%s\". " |
| "The former is already a sub_mode of the latter.", |
| regression_obj.name, sim_mode_obj_sub, sim_mode_obj.name) |
| sys.exit(1) |
| |
| # Check if sim_mode_obj is also passed on the command line, in |
| # which case, skip |
| if sim_mode_obj.name in sim_cfg.en_build_modes: |
| continue |
| |
| # Merge the build and run opts from the sim modes |
| regression_obj.build_opts.extend(sim_mode_obj.build_opts) |
| regression_obj.run_opts.extend(sim_mode_obj.run_opts) |
| |
| # Unpack the run_modes |
| # TODO: If there are other params other than run_opts throw an error and exit |
| found_run_mode_objs = Modes.find_and_merge_modes( |
| regression_obj, regression_obj.en_run_modes, run_modes, False) |
| |
| # Only merge the run_opts from the run_modes enabled |
| for run_mode_obj in found_run_mode_objs: |
| # Check if run_mode_obj is also passed on the command line, in |
| # which case, skip |
| if run_mode_obj.name in sim_cfg.en_run_modes: |
| continue |
| regression_obj.run_opts.extend(run_mode_obj.run_opts) |
| |
| # Unpack tests |
| if regression_obj.tests == []: |
| log.log(VERBOSE, |
| "Unpacking all tests in scope for regression \"%s\"", |
| regression_obj.name) |
| regression_obj.tests = sim_cfg.tests |
| regression_obj.test_names = Tests.item_names |
| |
| else: |
| tests_objs = [] |
| regression_obj.test_names = regression_obj.tests |
| for test in regression_obj.tests: |
| test_obj = Modes.find_mode(test, sim_cfg.tests) |
| if test_obj is None: |
| log.error( |
| "Test \"%s\" added to regression \"%s\" not found!", |
| test, regression_obj.name) |
| continue |
| tests_objs.append(test_obj) |
| regression_obj.tests = tests_objs |
| |
| # Return the list of tests |
| return regressions_objs |
| |
| def merge_regression_opts(self): |
| processed_build_modes = [] |
| for test in self.tests: |
| if test.build_mode.name not in processed_build_modes: |
| test.build_mode.build_opts.extend(self.build_opts) |
| processed_build_modes.append(test.build_mode.name) |
| test.run_opts.extend(self.run_opts) |
| |
| # Override reseed if available. |
| if self.reseed is not None: |
| test.reseed = self.reseed |