| # Copyright (c) 2017-2020 ARM Limited |
| # All rights reserved. |
| # |
| # The license below extends only to copyright in the software and shall |
| # not be construed as granting a license to any other intellectual |
| # property including but not limited to intellectual property relating |
| # to a hardware implementation of the functionality of the software |
| # licensed hereunder. You may use the software subject to the license |
| # terms below provided that you ensure that this notice is replicated |
| # unmodified and in its entirety in all distributions of the software, |
| # modified or unmodified, in source code or in binary form. |
| # |
| # Copyright (c) 2004-2006 The Regents of The University of Michigan |
| # Copyright (c) 2010-20013 Advanced Micro Devices, Inc. |
| # Copyright (c) 2013 Mark D. Hill and David A. Wood |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer; |
| # redistributions in binary form must reproduce the above copyright |
| # notice, this list of conditions and the following disclaimer in the |
| # documentation and/or other materials provided with the distribution; |
| # neither the name of the copyright holders nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| import sys |
| from types import FunctionType, MethodType, ModuleType |
| from functools import wraps |
| import inspect |
| |
| import m5 |
| from m5.util import * |
| from m5.util.pybind import * |
| # Use the pyfdt and not the helper class, because the fdthelper |
| # relies on the SimObject definition |
| from m5.ext.pyfdt import pyfdt |
| |
| # Have to import params up top since Param is referenced on initial |
| # load (when SimObject class references Param to create a class |
| # variable, the 'name' param)... |
| from m5.params import * |
| # There are a few things we need that aren't in params.__all__ since |
| # normal users don't need them |
| from m5.params import ParamDesc, VectorParamDesc, \ |
| isNullPointer, SimObjectVector, Port |
| |
| from m5.proxy import * |
| from m5.proxy import isproxy |
| |
| ##################################################################### |
| # |
| # M5 Python Configuration Utility |
| # |
| # The basic idea is to write simple Python programs that build Python |
| # objects corresponding to M5 SimObjects for the desired simulation |
| # configuration. For now, the Python emits a .ini file that can be |
| # parsed by M5. In the future, some tighter integration between M5 |
| # and the Python interpreter may allow bypassing the .ini file. |
| # |
| # Each SimObject class in M5 is represented by a Python class with the |
| # same name. The Python inheritance tree mirrors the M5 C++ tree |
| # (e.g., SimpleCPU derives from BaseCPU in both cases, and all |
| # SimObjects inherit from a single SimObject base class). To specify |
| # an instance of an M5 SimObject in a configuration, the user simply |
| # instantiates the corresponding Python object. The parameters for |
| # that SimObject are given by assigning to attributes of the Python |
| # object, either using keyword assignment in the constructor or in |
| # separate assignment statements. For example: |
| # |
| # cache = BaseCache(size='64KB') |
| # cache.hit_latency = 3 |
| # cache.assoc = 8 |
| # |
| # The magic lies in the mapping of the Python attributes for SimObject |
| # classes to the actual SimObject parameter specifications. This |
| # allows parameter validity checking in the Python code. Continuing |
| # the example above, the statements "cache.blurfl=3" or |
| # "cache.assoc='hello'" would both result in runtime errors in Python, |
| # since the BaseCache object has no 'blurfl' parameter and the 'assoc' |
| # parameter requires an integer, respectively. This magic is done |
| # primarily by overriding the special __setattr__ method that controls |
| # assignment to object attributes. |
| # |
| # Once a set of Python objects have been instantiated in a hierarchy, |
| # calling 'instantiate(obj)' (where obj is the root of the hierarchy) |
| # will generate a .ini file. |
| # |
| ##################################################################### |
| |
| # list of all SimObject classes |
| allClasses = {} |
| |
| # dict to look up SimObjects based on path |
| instanceDict = {} |
| |
| # Did any of the SimObjects lack a header file? |
| noCxxHeader = False |
| |
| def public_value(key, value): |
| return key.startswith('_') or \ |
| isinstance(value, (FunctionType, MethodType, ModuleType, |
| classmethod, type)) |
| |
| def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header): |
| entry_class = 'CxxConfigDirectoryEntry_%s' % name |
| param_class = '%sCxxConfigParams' % name |
| |
| code('#include "params/%s.hh"' % name) |
| |
| if not is_header: |
| for param in simobj._params.values(): |
| if isSimObjectClass(param.ptype): |
| code('#include "%s"' % param.ptype._value_dict['cxx_header']) |
| code('#include "params/%s.hh"' % param.ptype.__name__) |
| else: |
| param.ptype.cxx_ini_predecls(code) |
| |
| if is_header: |
| member_prefix = '' |
| end_of_decl = ';' |
| code('#include "sim/cxx_config.hh"') |
| code() |
| code('namespace gem5') |
| code('{') |
| code() |
| code('class ${param_class} : public CxxConfigParams,' |
| ' public ${name}Params') |
| code('{') |
| code(' private:') |
| code.indent() |
| code('class DirectoryEntry : public CxxConfigDirectoryEntry') |
| code('{') |
| code(' public:') |
| code.indent() |
| code('DirectoryEntry();'); |
| code() |
| code('CxxConfigParams *makeParamsObject() const') |
| code('{ return new ${param_class}; }') |
| code.dedent() |
| code('};') |
| code() |
| code.dedent() |
| code(' public:') |
| code.indent() |
| else: |
| member_prefix = '%s::' % param_class |
| end_of_decl = '' |
| code('#include "%s"' % simobj._value_dict['cxx_header']) |
| code('#include "base/str.hh"') |
| code('#include "cxx_config/${name}.hh"') |
| |
| code('namespace gem5') |
| code('{') |
| code() |
| code('${member_prefix}DirectoryEntry::DirectoryEntry()'); |
| code('{') |
| |
| def cxx_bool(b): |
| return 'true' if b else 'false' |
| |
| code.indent() |
| for param in simobj._params.values(): |
| is_vector = isinstance(param, m5.params.VectorParamDesc) |
| is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) |
| |
| code('parameters["%s"] = new ParamDesc("%s", %s, %s);' % |
| (param.name, param.name, cxx_bool(is_vector), |
| cxx_bool(is_simobj))); |
| |
| for port in simobj._ports.values(): |
| is_vector = isinstance(port, m5.params.VectorPort) |
| is_requestor = port.role == 'GEM5 REQUESTOR' |
| |
| code('ports["%s"] = new PortDesc("%s", %s, %s);' % |
| (port.name, port.name, cxx_bool(is_vector), |
| cxx_bool(is_requestor))) |
| |
| code.dedent() |
| code('}') |
| code() |
| |
| code('bool ${member_prefix}setSimObject(const std::string &name,') |
| code(' SimObject *simObject)${end_of_decl}') |
| |
| if not is_header: |
| code('{') |
| code.indent() |
| code('bool ret = true;') |
| code() |
| code('if (false) {') |
| for param in simobj._params.values(): |
| is_vector = isinstance(param, m5.params.VectorParamDesc) |
| is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) |
| |
| if is_simobj and not is_vector: |
| code('} else if (name == "${{param.name}}") {') |
| code.indent() |
| code('this->${{param.name}} = ' |
| 'dynamic_cast<${{param.ptype.cxx_type}}>(simObject);') |
| code('if (simObject && !this->${{param.name}})') |
| code(' ret = false;') |
| code.dedent() |
| code('} else {') |
| code(' ret = false;') |
| code('}') |
| code() |
| code('return ret;') |
| code.dedent() |
| code('}') |
| |
| code() |
| code('bool ${member_prefix}setSimObjectVector(' |
| 'const std::string &name,') |
| code(' const std::vector<SimObject *> &simObjects)${end_of_decl}') |
| |
| if not is_header: |
| code('{') |
| code.indent() |
| code('bool ret = true;') |
| code() |
| code('if (false) {') |
| for param in simobj._params.values(): |
| is_vector = isinstance(param, m5.params.VectorParamDesc) |
| is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) |
| |
| if is_simobj and is_vector: |
| code('} else if (name == "${{param.name}}") {') |
| code.indent() |
| code('this->${{param.name}}.clear();') |
| code('for (auto i = simObjects.begin(); ' |
| 'ret && i != simObjects.end(); i ++)') |
| code('{') |
| code.indent() |
| code('${{param.ptype.cxx_type}} object = ' |
| 'dynamic_cast<${{param.ptype.cxx_type}}>(*i);') |
| code('if (*i && !object)') |
| code(' ret = false;') |
| code('else') |
| code(' this->${{param.name}}.push_back(object);') |
| code.dedent() |
| code('}') |
| code.dedent() |
| code('} else {') |
| code(' ret = false;') |
| code('}') |
| code() |
| code('return ret;') |
| code.dedent() |
| code('}') |
| |
| code() |
| code('void ${member_prefix}setName(const std::string &name_)' |
| '${end_of_decl}') |
| |
| if not is_header: |
| code('{') |
| code.indent() |
| code('this->name = name_;') |
| code.dedent() |
| code('}') |
| |
| if is_header: |
| code('const std::string &${member_prefix}getName()') |
| code('{ return this->name; }') |
| |
| code() |
| code('bool ${member_prefix}setParam(const std::string &name,') |
| code(' const std::string &value, const Flags flags)${end_of_decl}') |
| |
| if not is_header: |
| code('{') |
| code.indent() |
| code('bool ret = true;') |
| code() |
| code('if (false) {') |
| for param in simobj._params.values(): |
| is_vector = isinstance(param, m5.params.VectorParamDesc) |
| is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) |
| |
| if not is_simobj and not is_vector: |
| code('} else if (name == "${{param.name}}") {') |
| code.indent() |
| param.ptype.cxx_ini_parse(code, |
| 'value', 'this->%s' % param.name, 'ret =') |
| code.dedent() |
| code('} else {') |
| code(' ret = false;') |
| code('}') |
| code() |
| code('return ret;') |
| code.dedent() |
| code('}') |
| |
| code() |
| code('bool ${member_prefix}setParamVector(' |
| 'const std::string &name,') |
| code(' const std::vector<std::string> &values,') |
| code(' const Flags flags)${end_of_decl}') |
| |
| if not is_header: |
| code('{') |
| code.indent() |
| code('bool ret = true;') |
| code() |
| code('if (false) {') |
| for param in simobj._params.values(): |
| is_vector = isinstance(param, m5.params.VectorParamDesc) |
| is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) |
| |
| if not is_simobj and is_vector: |
| code('} else if (name == "${{param.name}}") {') |
| code.indent() |
| code('${{param.name}}.clear();') |
| code('for (auto i = values.begin(); ' |
| 'ret && i != values.end(); i ++)') |
| code('{') |
| code.indent() |
| code('${{param.ptype.cxx_type}} elem;') |
| param.ptype.cxx_ini_parse(code, |
| '*i', 'elem', 'ret =') |
| code('if (ret)') |
| code(' this->${{param.name}}.push_back(elem);') |
| code.dedent() |
| code('}') |
| code.dedent() |
| code('} else {') |
| code(' ret = false;') |
| code('}') |
| code() |
| code('return ret;') |
| code.dedent() |
| code('}') |
| |
| code() |
| code('bool ${member_prefix}setPortConnectionCount(' |
| 'const std::string &name,') |
| code(' unsigned int count)${end_of_decl}') |
| |
| if not is_header: |
| code('{') |
| code.indent() |
| code('bool ret = true;') |
| code() |
| code('if (false)') |
| code(' ;') |
| for port in simobj._ports.values(): |
| code('else if (name == "${{port.name}}")') |
| code(' this->port_${{port.name}}_connection_count = count;') |
| code('else') |
| code(' ret = false;') |
| code() |
| code('return ret;') |
| code.dedent() |
| code('}') |
| |
| code() |
| code('SimObject *${member_prefix}simObjectCreate()${end_of_decl}') |
| |
| if not is_header: |
| code('{') |
| if hasattr(simobj, 'abstract') and simobj.abstract: |
| code(' return NULL;') |
| else: |
| code(' return this->create();') |
| code('}') |
| |
| if is_header: |
| code() |
| code('static CxxConfigDirectoryEntry' |
| ' *${member_prefix}makeDirectoryEntry()') |
| code('{ return new DirectoryEntry; }') |
| |
| if is_header: |
| code.dedent() |
| code('};') |
| |
| code('} // namespace gem5') |
| |
| # The metaclass for SimObject. This class controls how new classes |
| # that derive from SimObject are instantiated, and provides inherited |
| # class behavior (just like a class controls how instances of that |
| # class are instantiated, and provides inherited instance behavior). |
| class MetaSimObject(type): |
| # Attributes that can be set only at initialization time |
| init_keywords = { |
| 'abstract' : bool, |
| 'cxx_class' : str, |
| 'cxx_type' : str, |
| 'cxx_header' : str, |
| 'type' : str, |
| 'cxx_base' : (str, type(None)), |
| 'cxx_extra_bases' : list, |
| 'cxx_exports' : list, |
| 'cxx_param_exports' : list, |
| 'cxx_template_params' : list, |
| } |
| # Attributes that can be set any time |
| keywords = { 'check' : FunctionType } |
| |
| # __new__ is called before __init__, and is where the statements |
| # in the body of the class definition get loaded into the class's |
| # __dict__. We intercept this to filter out parameter & port assignments |
| # and only allow "private" attributes to be passed to the base |
| # __new__ (starting with underscore). |
| def __new__(mcls, name, bases, dict): |
| assert name not in allClasses, "SimObject %s already present" % name |
| |
| # Copy "private" attributes, functions, and classes to the |
| # official dict. Everything else goes in _init_dict to be |
| # filtered in __init__. |
| cls_dict = {} |
| value_dict = {} |
| cxx_exports = [] |
| for key,val in dict.items(): |
| try: |
| cxx_exports.append(getattr(val, "__pybind")) |
| except AttributeError: |
| pass |
| |
| if public_value(key, val): |
| cls_dict[key] = val |
| else: |
| # must be a param/port setting |
| value_dict[key] = val |
| if 'abstract' not in value_dict: |
| value_dict['abstract'] = False |
| if 'cxx_extra_bases' not in value_dict: |
| value_dict['cxx_extra_bases'] = [] |
| if 'cxx_exports' not in value_dict: |
| value_dict['cxx_exports'] = cxx_exports |
| else: |
| value_dict['cxx_exports'] += cxx_exports |
| if 'cxx_param_exports' not in value_dict: |
| value_dict['cxx_param_exports'] = [] |
| if 'cxx_template_params' not in value_dict: |
| value_dict['cxx_template_params'] = [] |
| cls_dict['_value_dict'] = value_dict |
| cls = super().__new__(mcls, name, bases, cls_dict) |
| if 'type' in value_dict: |
| allClasses[name] = cls |
| return cls |
| |
| # subclass initialization |
| def __init__(cls, name, bases, dict): |
| # calls type.__init__()... I think that's a no-op, but leave |
| # it here just in case it's not. |
| super().__init__(name, bases, dict) |
| |
| # initialize required attributes |
| |
| # class-only attributes |
| cls._params = multidict() # param descriptions |
| cls._ports = multidict() # port descriptions |
| |
| # Parameter names that are deprecated. Dict[str, DeprecatedParam] |
| # The key is the "old_name" so that when the old_name is used in |
| # python config files, we will use the DeprecatedParam object to |
| # translate to the new type. |
| cls._deprecated_params = multidict() |
| |
| # class or instance attributes |
| cls._values = multidict() # param values |
| cls._hr_values = multidict() # human readable param values |
| cls._children = multidict() # SimObject children |
| cls._port_refs = multidict() # port ref objects |
| cls._instantiated = False # really instantiated, cloned, or subclassed |
| cls._init_called = False # Used to check if __init__ overridden |
| |
| # We don't support multiple inheritance of sim objects. If you want |
| # to, you must fix multidict to deal with it properly. Non sim-objects |
| # are ok, though |
| bTotal = 0 |
| for c in bases: |
| if isinstance(c, MetaSimObject): |
| bTotal += 1 |
| if bTotal > 1: |
| raise TypeError( |
| "SimObjects do not support multiple inheritance") |
| |
| base = bases[0] |
| |
| # Set up general inheritance via multidicts. A subclass will |
| # inherit all its settings from the base class. The only time |
| # the following is not true is when we define the SimObject |
| # class itself (in which case the multidicts have no parent). |
| if isinstance(base, MetaSimObject): |
| cls._base = base |
| cls._params.parent = base._params |
| cls._ports.parent = base._ports |
| cls._deprecated_params.parent = base._deprecated_params |
| cls._values.parent = base._values |
| cls._hr_values.parent = base._hr_values |
| cls._children.parent = base._children |
| cls._port_refs.parent = base._port_refs |
| # mark base as having been subclassed |
| base._instantiated = True |
| else: |
| cls._base = None |
| |
| # default keyword values |
| if 'type' in cls._value_dict: |
| if 'cxx_class' not in cls._value_dict: |
| cls._value_dict['cxx_class'] = cls._value_dict['type'] |
| |
| cls._value_dict['cxx_type'] = '%s *' % cls._value_dict['cxx_class'] |
| |
| if 'cxx_header' not in cls._value_dict: |
| global noCxxHeader |
| noCxxHeader = True |
| warn("No header file specified for SimObject: %s", name) |
| |
| # Now process the _value_dict items. They could be defining |
| # new (or overriding existing) parameters or ports, setting |
| # class keywords (e.g., 'abstract'), or setting parameter |
| # values or port bindings. The first 3 can only be set when |
| # the class is defined, so we handle them here. The others |
| # can be set later too, so just emulate that by calling |
| # setattr(). |
| for key,val in cls._value_dict.items(): |
| # param descriptions |
| if isinstance(val, ParamDesc): |
| cls._new_param(key, val) |
| |
| # port objects |
| elif isinstance(val, Port): |
| cls._new_port(key, val) |
| |
| # Deprecated variable names |
| elif isinstance(val, DeprecatedParam): |
| new_name, new_val = cls._get_param_by_value(val.newParam) |
| # Note: We don't know the (string) name of this variable until |
| # here, so now we can finish setting up the dep_param. |
| val.oldName = key |
| val.newName = new_name |
| cls._deprecated_params[key] = val |
| |
| # init-time-only keywords |
| elif key in cls.init_keywords: |
| cls._set_keyword(key, val, cls.init_keywords[key]) |
| |
| # default: use normal path (ends up in __setattr__) |
| else: |
| setattr(cls, key, val) |
| |
| def _set_keyword(cls, keyword, val, kwtype): |
| if not isinstance(val, kwtype): |
| raise TypeError('keyword %s has bad type %s (expecting %s)' % \ |
| (keyword, type(val), kwtype)) |
| if isinstance(val, FunctionType): |
| val = classmethod(val) |
| type.__setattr__(cls, keyword, val) |
| |
| def _new_param(cls, name, pdesc): |
| # each param desc should be uniquely assigned to one variable |
| assert(not hasattr(pdesc, 'name')) |
| pdesc.name = name |
| cls._params[name] = pdesc |
| if hasattr(pdesc, 'default'): |
| cls._set_param(name, pdesc.default, pdesc) |
| |
| def _set_param(cls, name, value, param): |
| assert(param.name == name) |
| try: |
| hr_value = value |
| value = param.convert(value) |
| except Exception as e: |
| msg = "%s\nError setting param %s.%s to %s\n" % \ |
| (e, cls.__name__, name, value) |
| e.args = (msg, ) |
| raise |
| cls._values[name] = value |
| # if param value is a SimObject, make it a child too, so that |
| # it gets cloned properly when the class is instantiated |
| if isSimObjectOrVector(value) and not value.has_parent(): |
| cls._add_cls_child(name, value) |
| # update human-readable values of the param if it has a literal |
| # value and is not an object or proxy. |
| if not (isSimObjectOrVector(value) or\ |
| isinstance(value, m5.proxy.BaseProxy)): |
| cls._hr_values[name] = hr_value |
| |
| def _add_cls_child(cls, name, child): |
| # It's a little funky to have a class as a parent, but these |
| # objects should never be instantiated (only cloned, which |
| # clears the parent pointer), and this makes it clear that the |
| # object is not an orphan and can provide better error |
| # messages. |
| child.set_parent(cls, name) |
| if not isNullPointer(child): |
| cls._children[name] = child |
| |
| def _new_port(cls, name, port): |
| # each port should be uniquely assigned to one variable |
| assert(not hasattr(port, 'name')) |
| port.name = name |
| cls._ports[name] = port |
| |
| # same as _get_port_ref, effectively, but for classes |
| def _cls_get_port_ref(cls, attr): |
| # Return reference that can be assigned to another port |
| # via __setattr__. There is only ever one reference |
| # object per port, but we create them lazily here. |
| ref = cls._port_refs.get(attr) |
| if not ref: |
| ref = cls._ports[attr].makeRef(cls) |
| cls._port_refs[attr] = ref |
| return ref |
| |
| def _get_param_by_value(cls, value): |
| """Given an object, value, return the name and the value from the |
| internal list of parameter values. If this value can't be found, raise |
| a runtime error. This will search both the current object and its |
| parents. |
| """ |
| for k,v in cls._value_dict.items(): |
| if v == value: |
| return k,v |
| raise RuntimeError("Cannot find parameter {} in parameter list" |
| .format(value)) |
| |
| # Set attribute (called on foo.attr = value when foo is an |
| # instance of class cls). |
| def __setattr__(cls, attr, value): |
| # normal processing for private attributes |
| if public_value(attr, value): |
| type.__setattr__(cls, attr, value) |
| return |
| |
| if attr in cls.keywords: |
| cls._set_keyword(attr, value, cls.keywords[attr]) |
| return |
| |
| if attr in cls._ports: |
| cls._cls_get_port_ref(attr).connect(value) |
| return |
| |
| if isSimObjectOrSequence(value) and cls._instantiated: |
| raise RuntimeError( |
| "cannot set SimObject parameter '%s' after\n" \ |
| " class %s has been instantiated or subclassed" \ |
| % (attr, cls.__name__)) |
| |
| # check for param |
| param = cls._params.get(attr) |
| if param: |
| cls._set_param(attr, value, param) |
| return |
| |
| if isSimObjectOrSequence(value): |
| # If RHS is a SimObject, it's an implicit child assignment. |
| cls._add_cls_child(attr, coerceSimObjectOrVector(value)) |
| return |
| |
| # no valid assignment... raise exception |
| raise AttributeError( |
| "Class %s has no parameter \'%s\'" % (cls.__name__, attr)) |
| |
| def __getattr__(cls, attr): |
| if attr == 'cxx_class_path': |
| return cls.cxx_class.split('::') |
| |
| if attr == 'cxx_class_name': |
| return cls.cxx_class_path[-1] |
| |
| if attr == 'cxx_namespaces': |
| return cls.cxx_class_path[:-1] |
| |
| if attr == 'pybind_class': |
| return '_COLONS_'.join(cls.cxx_class_path) |
| |
| if attr in cls._values: |
| return cls._values[attr] |
| |
| if attr in cls._children: |
| return cls._children[attr] |
| |
| try: |
| return getattr(cls.getCCClass(), attr) |
| except AttributeError: |
| raise AttributeError( |
| "object '%s' has no attribute '%s'" % (cls.__name__, attr)) |
| |
| def __str__(cls): |
| return cls.__name__ |
| |
| def getCCClass(cls): |
| return getattr(m5.internal.params, cls.pybind_class) |
| |
| # See ParamValue.cxx_predecls for description. |
| def cxx_predecls(cls, code): |
| code('#include "params/$cls.hh"') |
| |
| def pybind_predecls(cls, code): |
| code('#include "${{cls.cxx_header}}"') |
| |
| def params_create_decl(cls, code, python_enabled): |
| py_class_name = cls.pybind_class |
| |
| # The 'local' attribute restricts us to the params declared in |
| # the object itself, not including inherited params (which |
| # will also be inherited from the base class's param struct |
| # here). Sort the params based on their key |
| params = list(map(lambda k_v: k_v[1], |
| sorted(cls._params.local.items()))) |
| ports = cls._ports.local |
| |
| # only include pybind if python is enabled in the build |
| if python_enabled: |
| |
| code('''#include "pybind11/pybind11.h" |
| #include "pybind11/stl.h" |
| |
| #include <type_traits> |
| |
| #include "base/compiler.hh" |
| #include "params/$cls.hh" |
| #include "sim/init.hh" |
| #include "sim/sim_object.hh" |
| |
| #include "${{cls.cxx_header}}" |
| |
| ''') |
| else: |
| code(''' |
| #include <type_traits> |
| |
| #include "base/compiler.hh" |
| #include "params/$cls.hh" |
| |
| #include "${{cls.cxx_header}}" |
| |
| ''') |
| # only include the python params code if python is enabled. |
| if python_enabled: |
| for param in params: |
| param.pybind_predecls(code) |
| |
| code('''namespace py = pybind11; |
| |
| namespace gem5 |
| { |
| |
| static void |
| module_init(py::module_ &m_internal) |
| { |
| py::module_ m = m_internal.def_submodule("param_${cls}"); |
| ''') |
| code.indent() |
| if cls._base: |
| code('py::class_<${cls}Params, ${{cls._base.type}}Params, ' \ |
| 'std::unique_ptr<${{cls}}Params, py::nodelete>>(' \ |
| 'm, "${cls}Params")') |
| else: |
| code('py::class_<${cls}Params, ' \ |
| 'std::unique_ptr<${cls}Params, py::nodelete>>(' \ |
| 'm, "${cls}Params")') |
| |
| code.indent() |
| if not hasattr(cls, 'abstract') or not cls.abstract: |
| code('.def(py::init<>())') |
| code('.def("create", &${cls}Params::create)') |
| |
| param_exports = cls.cxx_param_exports + [ |
| PyBindProperty(k) |
| for k, v in sorted(cls._params.local.items()) |
| ] + [ |
| PyBindProperty("port_%s_connection_count" % port.name) |
| for port in ports.values() |
| ] |
| for exp in param_exports: |
| exp.export(code, "%sParams" % cls) |
| |
| code(';') |
| code() |
| code.dedent() |
| |
| bases = [] |
| if 'cxx_base' in cls._value_dict: |
| # If the c++ base class implied by python inheritance was |
| # overridden, use that value. |
| if cls.cxx_base: |
| bases.append(cls.cxx_base) |
| elif cls._base: |
| # If not and if there was a SimObject base, use its c++ class |
| # as this class' base. |
| bases.append(cls._base.cxx_class) |
| # Add in any extra bases that were requested. |
| bases.extend(cls.cxx_extra_bases) |
| |
| if bases: |
| base_str = ", ".join(bases) |
| code('py::class_<${{cls.cxx_class}}, ${base_str}, ' \ |
| 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \ |
| 'm, "${py_class_name}")') |
| else: |
| code('py::class_<${{cls.cxx_class}}, ' \ |
| 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \ |
| 'm, "${py_class_name}")') |
| code.indent() |
| for exp in cls.cxx_exports: |
| exp.export(code, cls.cxx_class) |
| code(';') |
| code.dedent() |
| code() |
| code.dedent() |
| code('}') |
| code() |
| code('static EmbeddedPyBind ' |
| 'embed_obj("${0}", module_init, "${1}");', |
| cls, cls._base.type if cls._base else "") |
| code() |
| code('} // namespace gem5') |
| |
| # include the create() methods whether or not python is enabled. |
| if not hasattr(cls, 'abstract') or not cls.abstract: |
| if 'type' in cls.__dict__: |
| code() |
| code('namespace gem5') |
| code('{') |
| code() |
| code('namespace') |
| code('{') |
| code() |
| # If we can't define a default create() method for this params |
| # struct because the SimObject doesn't have the right |
| # constructor, use template magic to make it so we're actually |
| # defining a create method for this class instead. |
| code('class Dummy${cls}ParamsClass') |
| code('{') |
| code(' public:') |
| code(' ${{cls.cxx_class}} *create() const;') |
| code('};') |
| code() |
| code('template <class CxxClass, class Enable=void>') |
| code('class Dummy${cls}Shunt;') |
| code() |
| # This version directs to the real Params struct and the |
| # default behavior of create if there's an appropriate |
| # constructor. |
| code('template <class CxxClass>') |
| code('class Dummy${cls}Shunt<CxxClass, std::enable_if_t<') |
| code(' std::is_constructible_v<CxxClass,') |
| code(' const ${cls}Params &>>>') |
| code('{') |
| code(' public:') |
| code(' using Params = ${cls}Params;') |
| code(' static ${{cls.cxx_class}} *') |
| code(' create(const Params &p)') |
| code(' {') |
| code(' return new CxxClass(p);') |
| code(' }') |
| code('};') |
| code() |
| # This version diverts to the DummyParamsClass and a dummy |
| # implementation of create if the appropriate constructor does |
| # not exist. |
| code('template <class CxxClass>') |
| code('class Dummy${cls}Shunt<CxxClass, std::enable_if_t<') |
| code(' !std::is_constructible_v<CxxClass,') |
| code(' const ${cls}Params &>>>') |
| code('{') |
| code(' public:') |
| code(' using Params = Dummy${cls}ParamsClass;') |
| code(' static ${{cls.cxx_class}} *') |
| code(' create(const Params &p)') |
| code(' {') |
| code(' return nullptr;') |
| code(' }') |
| code('};') |
| code() |
| code('} // anonymous namespace') |
| code() |
| # An implementation of either the real Params struct's create |
| # method, or the Dummy one. Either an implementation is |
| # mandantory since this was shunted off to the dummy class, or |
| # one is optional which will override this weak version. |
| code('[[maybe_unused]] ${{cls.cxx_class}} *') |
| code('Dummy${cls}Shunt<${{cls.cxx_class}}>::Params::create() ' |
| 'const') |
| code('{') |
| code(' return Dummy${cls}Shunt<${{cls.cxx_class}}>::') |
| code(' create(*this);') |
| code('}') |
| code() |
| code('} // namespace gem5') |
| |
| _warned_about_nested_templates = False |
| |
| # Generate the C++ declaration (.hh file) for this SimObject's |
| # param struct. Called from src/SConscript. |
| def cxx_param_decl(cls, code): |
| # The 'local' attribute restricts us to the params declared in |
| # the object itself, not including inherited params (which |
| # will also be inherited from the base class's param struct |
| # here). Sort the params based on their key |
| params = list(map(lambda k_v: k_v[1], sorted(cls._params.local.items()))) |
| ports = cls._ports.local |
| try: |
| ptypes = [p.ptype for p in params] |
| except: |
| print(cls, p, p.ptype_str) |
| print(params) |
| raise |
| |
| class CxxClass(object): |
| def __init__(self, sig, template_params=[]): |
| # Split the signature into its constituent parts. This could |
| # potentially be done with regular expressions, but |
| # it's simple enough to pick appart a class signature |
| # manually. |
| parts = sig.split('<', 1) |
| base = parts[0] |
| t_args = [] |
| if len(parts) > 1: |
| # The signature had template arguments. |
| text = parts[1].rstrip(' \t\n>') |
| arg = '' |
| # Keep track of nesting to avoid splitting on ","s embedded |
| # in the arguments themselves. |
| depth = 0 |
| for c in text: |
| if c == '<': |
| depth = depth + 1 |
| if depth > 0 and not \ |
| self._warned_about_nested_templates: |
| self._warned_about_nested_templates = True |
| print('Nested template argument in cxx_class.' |
| ' This feature is largely untested and ' |
| ' may not work.') |
| elif c == '>': |
| depth = depth - 1 |
| elif c == ',' and depth == 0: |
| t_args.append(arg.strip()) |
| arg = '' |
| else: |
| arg = arg + c |
| if arg: |
| t_args.append(arg.strip()) |
| # Split the non-template part on :: boundaries. |
| class_path = base.split('::') |
| |
| # The namespaces are everything except the last part of the |
| # class path. |
| self.namespaces = class_path[:-1] |
| # And the class name is the last part. |
| self.name = class_path[-1] |
| |
| self.template_params = template_params |
| self.template_arguments = [] |
| # Iterate through the template arguments and their values. This |
| # will likely break if parameter packs are used. |
| for arg, param in zip(t_args, template_params): |
| type_keys = ('class', 'typename') |
| # If a parameter is a type, parse it recursively. Otherwise |
| # assume it's a constant, and store it verbatim. |
| if any(param.strip().startswith(kw) for kw in type_keys): |
| self.template_arguments.append(CxxClass(arg)) |
| else: |
| self.template_arguments.append(arg) |
| |
| def declare(self, code): |
| # First declare any template argument types. |
| for arg in self.template_arguments: |
| if isinstance(arg, CxxClass): |
| arg.declare(code) |
| # Re-open the target namespace. |
| for ns in self.namespaces: |
| code('namespace $ns {') |
| # If this is a class template... |
| if self.template_params: |
| code('template <${{", ".join(self.template_params)}}>') |
| # The actual class declaration. |
| code('class ${{self.name}};') |
| # Close the target namespaces. |
| for ns in reversed(self.namespaces): |
| code('} // namespace $ns') |
| |
| code('''\ |
| #ifndef __PARAMS__${cls}__ |
| #define __PARAMS__${cls}__ |
| |
| ''') |
| |
| |
| # The base SimObject has a couple of params that get |
| # automatically set from Python without being declared through |
| # the normal Param mechanism; we slip them in here (needed |
| # predecls now, actual declarations below) |
| if cls == SimObject: |
| code('''#include <string>''') |
| |
| cxx_class = CxxClass(cls._value_dict['cxx_class'], |
| cls._value_dict['cxx_template_params']) |
| |
| # A forward class declaration is sufficient since we are just |
| # declaring a pointer. |
| cxx_class.declare(code) |
| |
| for param in params: |
| param.cxx_predecls(code) |
| for port in ports.values(): |
| port.cxx_predecls(code) |
| code() |
| |
| if cls._base: |
| code('#include "params/${{cls._base.type}}.hh"') |
| code() |
| |
| for ptype in ptypes: |
| if issubclass(ptype, Enum): |
| code('#include "enums/${{ptype.__name__}}.hh"') |
| code() |
| |
| code('namespace gem5') |
| code('{') |
| code('') |
| |
| # now generate the actual param struct |
| code("struct ${cls}Params") |
| if cls._base: |
| code(" : public ${{cls._base.type}}Params") |
| code("{") |
| if not hasattr(cls, 'abstract') or not cls.abstract: |
| if 'type' in cls.__dict__: |
| code(" ${{cls.cxx_type}} create() const;") |
| |
| code.indent() |
| if cls == SimObject: |
| code(''' |
| SimObjectParams() {} |
| virtual ~SimObjectParams() {} |
| |
| std::string name; |
| ''') |
| |
| for param in params: |
| param.cxx_decl(code) |
| for port in ports.values(): |
| port.cxx_decl(code) |
| |
| code.dedent() |
| code('};') |
| code() |
| code('} // namespace gem5') |
| |
| code() |
| code('#endif // __PARAMS__${cls}__') |
| return code |
| |
| # Generate the C++ declaration/definition files for this SimObject's |
| # param struct to allow C++ initialisation |
| def cxx_config_param_file(cls, code, is_header): |
| createCxxConfigDirectoryEntryFile(code, cls.__name__, cls, is_header) |
| return code |
| |
| # This *temporary* definition is required to support calls from the |
| # SimObject class definition to the MetaSimObject methods (in |
| # particular _set_param, which gets called for parameters with default |
| # values defined on the SimObject class itself). It will get |
| # overridden by the permanent definition (which requires that |
| # SimObject be defined) lower in this file. |
| def isSimObjectOrVector(value): |
| return False |
| |
| def cxxMethod(*args, **kwargs): |
| """Decorator to export C++ functions to Python""" |
| |
| def decorate(func): |
| name = func.__name__ |
| override = kwargs.get("override", False) |
| cxx_name = kwargs.get("cxx_name", name) |
| return_value_policy = kwargs.get("return_value_policy", None) |
| static = kwargs.get("static", False) |
| |
| args, varargs, keywords, defaults = inspect.getargspec(func) |
| if varargs or keywords: |
| raise ValueError("Wrapped methods must not contain variable " \ |
| "arguments") |
| |
| # Create tuples of (argument, default) |
| if defaults: |
| args = args[:-len(defaults)] + \ |
| list(zip(args[-len(defaults):], defaults)) |
| # Don't include self in the argument list to PyBind |
| args = args[1:] |
| |
| |
| @wraps(func) |
| def cxx_call(self, *args, **kwargs): |
| ccobj = self.getCCClass() if static else self.getCCObject() |
| return getattr(ccobj, name)(*args, **kwargs) |
| |
| @wraps(func) |
| def py_call(self, *args, **kwargs): |
| return func(self, *args, **kwargs) |
| |
| f = py_call if override else cxx_call |
| f.__pybind = PyBindMethod(name, cxx_name=cxx_name, args=args, |
| return_value_policy=return_value_policy, |
| static=static) |
| |
| return f |
| |
| if len(args) == 0: |
| return decorate |
| elif len(args) == 1 and len(kwargs) == 0: |
| return decorate(*args) |
| else: |
| raise TypeError("One argument and no kwargs, or only kwargs expected") |
| |
| # This class holds information about each simobject parameter |
| # that should be displayed on the command line for use in the |
| # configuration system. |
| class ParamInfo(object): |
| def __init__(self, type, desc, type_str, example, default_val, access_str): |
| self.type = type |
| self.desc = desc |
| self.type_str = type_str |
| self.example_str = example |
| self.default_val = default_val |
| # The string representation used to access this param through python. |
| # The method to access this parameter presented on the command line may |
| # be different, so this needs to be stored for later use. |
| self.access_str = access_str |
| self.created = True |
| |
| # Make it so we can only set attributes at initialization time |
| # and effectively make this a const object. |
| def __setattr__(self, name, value): |
| if not "created" in self.__dict__: |
| self.__dict__[name] = value |
| |
| class SimObjectCliWrapperException(Exception): |
| def __init__(self, message): |
| super().__init__(message) |
| |
| class SimObjectCliWrapper(object): |
| """ |
| Wrapper class to restrict operations that may be done |
| from the command line on SimObjects. |
| |
| Only parameters may be set, and only children may be accessed. |
| |
| Slicing allows for multiple simultaneous assignment of items in |
| one statement. |
| """ |
| |
| def __init__(self, sim_objects): |
| self.__dict__['_sim_objects'] = list(sim_objects) |
| |
| def __getattr__(self, key): |
| return SimObjectCliWrapper(sim_object._children[key] |
| for sim_object in self._sim_objects) |
| |
| def __setattr__(self, key, val): |
| for sim_object in self._sim_objects: |
| if key in sim_object._params: |
| if sim_object._params[key].isCmdLineSettable(): |
| setattr(sim_object, key, val) |
| else: |
| raise SimObjectCliWrapperException( |
| 'tried to set or unsettable' \ |
| 'object parameter: ' + key) |
| else: |
| raise SimObjectCliWrapperException( |
| 'tried to set or access non-existent' \ |
| 'object parameter: ' + key) |
| |
| def __getitem__(self, idx): |
| """ |
| Extends the list() semantics to also allow tuples, |
| for example object[1, 3] selects items 1 and 3. |
| """ |
| out = [] |
| if isinstance(idx, tuple): |
| for t in idx: |
| out.extend(self[t]._sim_objects) |
| else: |
| if isinstance(idx, int): |
| _range = range(idx, idx + 1) |
| elif not isinstance(idx, slice): |
| raise SimObjectCliWrapperException( \ |
| 'invalid index type: ' + repr(idx)) |
| for sim_object in self._sim_objects: |
| if isinstance(idx, slice): |
| _range = range(*idx.indices(len(sim_object))) |
| out.extend(sim_object[i] for i in _range) |
| return SimObjectCliWrapper(out) |
| |
| def __iter__(self): |
| return iter(self._sim_objects) |
| |
| # The SimObject class is the root of the special hierarchy. Most of |
| # the code in this class deals with the configuration hierarchy itself |
| # (parent/child node relationships). |
| class SimObject(object, metaclass=MetaSimObject): |
| # Specify metaclass. Any class inheriting from SimObject will |
| # get this metaclass. |
| type = 'SimObject' |
| abstract = True |
| |
| cxx_header = "sim/sim_object.hh" |
| cxx_class = 'gem5::SimObject' |
| cxx_extra_bases = [ "Drainable", "Serializable", "statistics::Group" ] |
| eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index") |
| |
| cxx_exports = [ |
| PyBindMethod("init"), |
| PyBindMethod("initState"), |
| PyBindMethod("memInvalidate"), |
| PyBindMethod("memWriteback"), |
| PyBindMethod("regProbePoints"), |
| PyBindMethod("regProbeListeners"), |
| PyBindMethod("startup"), |
| ] |
| |
| cxx_param_exports = [ |
| PyBindProperty("name"), |
| ] |
| |
| @cxxMethod |
| def loadState(self, cp): |
| """Load SimObject state from a checkpoint""" |
| pass |
| |
| # Returns a dict of all the option strings that can be |
| # generated as command line options for this simobject instance |
| # by tracing all reachable params in the top level instance and |
| # any children it contains. |
| def enumerateParams(self, flags_dict = {}, |
| cmd_line_str = "", access_str = ""): |
| if hasattr(self, "_paramEnumed"): |
| print("Cycle detected enumerating params") |
| else: |
| self._paramEnumed = True |
| # Scan the children first to pick up all the objects in this SimObj |
| for keys in self._children: |
| child = self._children[keys] |
| next_cmdline_str = cmd_line_str + keys |
| next_access_str = access_str + keys |
| if not isSimObjectVector(child): |
| next_cmdline_str = next_cmdline_str + "." |
| next_access_str = next_access_str + "." |
| flags_dict = child.enumerateParams(flags_dict, |
| next_cmdline_str, |
| next_access_str) |
| |
| # Go through the simple params in the simobject in this level |
| # of the simobject hierarchy and save information about the |
| # parameter to be used for generating and processing command line |
| # options to the simulator to set these parameters. |
| for keys,values in self._params.items(): |
| if values.isCmdLineSettable(): |
| type_str = '' |
| ex_str = values.example_str() |
| ptype = None |
| if isinstance(values, VectorParamDesc): |
| type_str = 'Vector_%s' % values.ptype_str |
| ptype = values |
| else: |
| type_str = '%s' % values.ptype_str |
| ptype = values.ptype |
| |
| if keys in self._hr_values\ |
| and keys in self._values\ |
| and not isinstance(self._values[keys], |
| m5.proxy.BaseProxy): |
| cmd_str = cmd_line_str + keys |
| acc_str = access_str + keys |
| flags_dict[cmd_str] = ParamInfo(ptype, |
| self._params[keys].desc, type_str, ex_str, |
| values.pretty_print(self._hr_values[keys]), |
| acc_str) |
| elif not keys in self._hr_values\ |
| and not keys in self._values: |
| # Empty param |
| cmd_str = cmd_line_str + keys |
| acc_str = access_str + keys |
| flags_dict[cmd_str] = ParamInfo(ptype, |
| self._params[keys].desc, |
| type_str, ex_str, '', acc_str) |
| |
| return flags_dict |
| |
| # Initialize new instance. For objects with SimObject-valued |
| # children, we need to recursively clone the classes represented |
| # by those param values as well in a consistent "deep copy"-style |
| # fashion. That is, we want to make sure that each instance is |
| # cloned only once, and that if there are multiple references to |
| # the same original object, we end up with the corresponding |
| # cloned references all pointing to the same cloned instance. |
| def __init__(self, **kwargs): |
| ancestor = kwargs.get('_ancestor') |
| memo_dict = kwargs.get('_memo') |
| if memo_dict is None: |
| # prepare to memoize any recursively instantiated objects |
| memo_dict = {} |
| elif ancestor: |
| # memoize me now to avoid problems with recursive calls |
| memo_dict[ancestor] = self |
| |
| if not ancestor: |
| ancestor = self.__class__ |
| ancestor._instantiated = True |
| |
| # initialize required attributes |
| self._parent = None |
| self._name = None |
| self._ccObject = None # pointer to C++ object |
| self._ccParams = None |
| self._instantiated = False # really "cloned" |
| self._init_called = True # Checked so subclasses don't forget __init__ |
| |
| # Clone children specified at class level. No need for a |
| # multidict here since we will be cloning everything. |
| # Do children before parameter values so that children that |
| # are also param values get cloned properly. |
| self._children = {} |
| for key,val in ancestor._children.items(): |
| self.add_child(key, val(_memo=memo_dict)) |
| |
| # Inherit parameter values from class using multidict so |
| # individual value settings can be overridden but we still |
| # inherit late changes to non-overridden class values. |
| self._values = multidict(ancestor._values) |
| self._hr_values = multidict(ancestor._hr_values) |
| # clone SimObject-valued parameters |
| for key,val in ancestor._values.items(): |
| val = tryAsSimObjectOrVector(val) |
| if val is not None: |
| self._values[key] = val(_memo=memo_dict) |
| |
| # clone port references. no need to use a multidict here |
| # since we will be creating new references for all ports. |
| self._port_refs = {} |
| for key,val in ancestor._port_refs.items(): |
| self._port_refs[key] = val.clone(self, memo_dict) |
| # apply attribute assignments from keyword args, if any |
| for key,val in kwargs.items(): |
| setattr(self, key, val) |
| |
| def _check_init(self): |
| """Utility function to check to make sure that all subclasses call |
| __init__ |
| """ |
| if not self._init_called: |
| raise RuntimeError(f"{str(self.__class__)} is missing a call " |
| "to super().__init__()") |
| |
| # "Clone" the current instance by creating another instance of |
| # this instance's class, but that inherits its parameter values |
| # and port mappings from the current instance. If we're in a |
| # "deep copy" recursive clone, check the _memo dict to see if |
| # we've already cloned this instance. |
| def __call__(self, **kwargs): |
| memo_dict = kwargs.get('_memo') |
| if memo_dict is None: |
| # no memo_dict: must be top-level clone operation. |
| # this is only allowed at the root of a hierarchy |
| if self._parent: |
| raise RuntimeError("attempt to clone object %s " \ |
| "not at the root of a tree (parent = %s)" \ |
| % (self, self._parent)) |
| # create a new dict and use that. |
| memo_dict = {} |
| kwargs['_memo'] = memo_dict |
| elif self in memo_dict: |
| # clone already done & memoized |
| return memo_dict[self] |
| return self.__class__(_ancestor = self, **kwargs) |
| |
| def _get_port_ref(self, attr): |
| # Return reference that can be assigned to another port |
| # via __setattr__. There is only ever one reference |
| # object per port, but we create them lazily here. |
| ref = self._port_refs.get(attr) |
| if ref == None: |
| ref = self._ports[attr].makeRef(self) |
| self._port_refs[attr] = ref |
| return ref |
| |
| def __getattr__(self, attr): |
| # Check for infinite recursion. If this SimObject hasn't been |
| # initialized with SimObject.__init__ this function will experience an |
| # infinite recursion checking for attributes that don't exist. |
| self._check_init() |
| |
| if attr in self._deprecated_params: |
| dep_param = self._deprecated_params[attr] |
| dep_param.printWarning(self._name, self.__class__.__name__) |
| return getattr(self, self._deprecated_params[attr].newName) |
| |
| if attr in self._ports: |
| return self._get_port_ref(attr) |
| |
| if attr in self._values: |
| return self._values[attr] |
| |
| if attr in self._children: |
| return self._children[attr] |
| |
| # If the attribute exists on the C++ object, transparently |
| # forward the reference there. This is typically used for |
| # methods exported to Python (e.g., init(), and startup()) |
| if self._ccObject and hasattr(self._ccObject, attr): |
| return getattr(self._ccObject, attr) |
| |
| err_string = "object '%s' has no attribute '%s'" \ |
| % (self.__class__.__name__, attr) |
| |
| if not self._ccObject: |
| err_string += "\n (C++ object is not yet constructed," \ |
| " so wrapped C++ methods are unavailable.)" |
| |
| raise AttributeError(err_string) |
| |
| # Set attribute (called on foo.attr = value when foo is an |
| # instance of class cls). |
| def __setattr__(self, attr, value): |
| # normal processing for private attributes |
| if attr.startswith('_'): |
| object.__setattr__(self, attr, value) |
| return |
| |
| if attr in self._deprecated_params: |
| dep_param = self._deprecated_params[attr] |
| dep_param.printWarning(self._name, self.__class__.__name__) |
| return setattr(self, self._deprecated_params[attr].newName, value) |
| |
| if attr in self._ports: |
| # set up port connection |
| self._get_port_ref(attr).connect(value) |
| return |
| |
| param = self._params.get(attr) |
| if param: |
| try: |
| hr_value = value |
| value = param.convert(value) |
| except Exception as e: |
| msg = "%s\nError setting param %s.%s to %s\n" % \ |
| (e, self.__class__.__name__, attr, value) |
| e.args = (msg, ) |
| raise |
| self._values[attr] = value |
| |
| # If we assign NULL to an attr that is a SimObject, |
| # remove the corresponding children |
| if attr in self._children and isNullPointer(value): |
| self.clear_child(attr) |
| |
| # implicitly parent unparented objects assigned as params |
| if isSimObjectOrVector(value) and not value.has_parent(): |
| self.add_child(attr, value) |
| # set the human-readable value dict if this is a param |
| # with a literal value and is not being set as an object |
| # or proxy. |
| if not (isSimObjectOrVector(value) or\ |
| isinstance(value, m5.proxy.BaseProxy)): |
| self._hr_values[attr] = hr_value |
| |
| return |
| |
| # if RHS is a SimObject, it's an implicit child assignment |
| if isSimObjectOrSequence(value): |
| self.add_child(attr, value) |
| return |
| |
| # no valid assignment... raise exception |
| raise AttributeError("Class %s has no parameter %s" \ |
| % (self.__class__.__name__, attr)) |
| |
| |
| # this hack allows tacking a '[0]' onto parameters that may or may |
| # not be vectors, and always getting the first element (e.g. cpus) |
| def __getitem__(self, key): |
| if key == 0: |
| return self |
| raise IndexError("Non-zero index '%s' to SimObject" % key) |
| |
| # this hack allows us to iterate over a SimObject that may |
| # not be a vector, so we can call a loop over it and get just one |
| # element. |
| def __len__(self): |
| return 1 |
| |
| # Also implemented by SimObjectVector |
| def clear_parent(self, old_parent): |
| assert self._parent is old_parent |
| self._parent = None |
| |
| # Also implemented by SimObjectVector |
| def set_parent(self, parent, name): |
| self._parent = parent |
| self._name = name |
| |
| # Return parent object of this SimObject, not implemented by |
| # SimObjectVector because the elements in a SimObjectVector may not share |
| # the same parent |
| def get_parent(self): |
| return self._parent |
| |
| # Also implemented by SimObjectVector |
| def get_name(self): |
| return self._name |
| |
| # Also implemented by SimObjectVector |
| def has_parent(self): |
| return self._parent is not None |
| |
| # clear out child with given name. This code is not likely to be exercised. |
| # See comment in add_child. |
| def clear_child(self, name): |
| child = self._children[name] |
| child.clear_parent(self) |
| del self._children[name] |
| |
| # Add a new child to this object. |
| def add_child(self, name, child): |
| child = coerceSimObjectOrVector(child) |
| if child.has_parent(): |
| warn(f"{self}.{name} already has parent not resetting parent.\n" |
| f"\tNote: {name} is not a parameter of {type(self).__name__}") |
| warn(f"(Previously declared as {child._parent}.{name}") |
| return |
| if name in self._children: |
| # This code path had an undiscovered bug that would make it fail |
| # at runtime. It had been here for a long time and was only |
| # exposed by a buggy script. Changes here will probably not be |
| # exercised without specialized testing. |
| self.clear_child(name) |
| if not isNullPointer(child): |
| child.set_parent(self, name) |
| self._children[name] = child |
| |
| # Take SimObject-valued parameters that haven't been explicitly |
| # assigned as children and make them children of the object that |
| # they were assigned to as a parameter value. This guarantees |
| # that when we instantiate all the parameter objects we're still |
| # inside the configuration hierarchy. |
| def adoptOrphanParams(self): |
| for key,val in self._values.items(): |
| if not isSimObjectVector(val) and isSimObjectSequence(val): |
| # need to convert raw SimObject sequences to |
| # SimObjectVector class so we can call has_parent() |
| val = SimObjectVector(val) |
| self._values[key] = val |
| if isSimObjectOrVector(val) and not val.has_parent(): |
| warn("%s adopting orphan SimObject param '%s'", self, key) |
| self.add_child(key, val) |
| |
| def path(self): |
| if not self._parent: |
| return '<orphan %s>' % self.__class__ |
| elif isinstance(self._parent, MetaSimObject): |
| return str(self.__class__) |
| |
| ppath = self._parent.path() |
| if ppath == 'root': |
| return self._name |
| return ppath + "." + self._name |
| |
| def path_list(self): |
| if self._parent: |
| return self._parent.path_list() + [ self._name, ] |
| else: |
| # Don't include the root node |
| return [] |
| |
| def __str__(self): |
| return self.path() |
| |
| def config_value(self): |
| return self.path() |
| |
| def ini_str(self): |
| return self.path() |
| |
| def find_any(self, ptype): |
| if isinstance(self, ptype): |
| return self, True |
| |
| found_obj = None |
| for child in self._children.values(): |
| visited = False |
| if hasattr(child, '_visited'): |
| visited = getattr(child, '_visited') |
| |
| if isinstance(child, ptype) and not visited: |
| if found_obj != None and child != found_obj: |
| raise AttributeError( |
| 'parent.any matched more than one: %s %s' % \ |
| (found_obj.path, child.path)) |
| found_obj = child |
| # search param space |
| for pname,pdesc in self._params.items(): |
| if issubclass(pdesc.ptype, ptype): |
| match_obj = self._values[pname] |
| if found_obj != None and found_obj != match_obj: |
| raise AttributeError( |
| 'parent.any matched more than one: %s and %s' % \ |
| (found_obj.path, match_obj.path)) |
| found_obj = match_obj |
| return found_obj, found_obj != None |
| |
| def find_all(self, ptype): |
| all = {} |
| # search children |
| for child in self._children.values(): |
| # a child could be a list, so ensure we visit each item |
| if isinstance(child, list): |
| children = child |
| else: |
| children = [child] |
| |
| for child in children: |
| if isinstance(child, ptype) and not isproxy(child) and \ |
| not isNullPointer(child): |
| all[child] = True |
| if isSimObject(child): |
| # also add results from the child itself |
| child_all, done = child.find_all(ptype) |
| all.update(dict(zip(child_all, [done] * len(child_all)))) |
| # search param space |
| for pname,pdesc in self._params.items(): |
| if issubclass(pdesc.ptype, ptype): |
| match_obj = self._values[pname] |
| if not isproxy(match_obj) and not isNullPointer(match_obj): |
| all[match_obj] = True |
| # Also make sure to sort the keys based on the objects' path to |
| # ensure that the order is the same on all hosts |
| return sorted(all.keys(), key = lambda o: o.path()), True |
| |
| def unproxy(self, base): |
| return self |
| |
| def unproxyParams(self): |
| for param in self._params.keys(): |
| value = self._values.get(param) |
| if value != None and isproxy(value): |
| try: |
| value = value.unproxy(self) |
| except: |
| print("Error in unproxying param '%s' of %s" % |
| (param, self.path())) |
| raise |
| setattr(self, param, value) |
| |
| # Unproxy ports in sorted order so that 'append' operations on |
| # vector ports are done in a deterministic fashion. |
| port_names = list(self._ports.keys()) |
| port_names.sort() |
| for port_name in port_names: |
| port = self._port_refs.get(port_name) |
| if port != None: |
| port.unproxy(self) |
| |
| def print_ini(self, ini_file): |
| print('[' + self.path() + ']', file=ini_file) # .ini section header |
| |
| instanceDict[self.path()] = self |
| |
| if hasattr(self, 'type'): |
| print('type=%s' % self.type, file=ini_file) |
| |
| if len(self._children.keys()): |
| print('children=%s' % |
| ' '.join(self._children[n].get_name() |
| for n in sorted(self._children.keys())), |
| file=ini_file) |
| |
| for param in sorted(self._params.keys()): |
| value = self._values.get(param) |
| if value != None: |
| print('%s=%s' % (param, self._values[param].ini_str()), |
| file=ini_file) |
| |
| for port_name in sorted(self._ports.keys()): |
| port = self._port_refs.get(port_name, None) |
| if port != None: |
| print('%s=%s' % (port_name, port.ini_str()), file=ini_file) |
| |
| print(file=ini_file) # blank line between objects |
| |
| # generate a tree of dictionaries expressing all the parameters in the |
| # instantiated system for use by scripts that want to do power, thermal |
| # visualization, and other similar tasks |
| def get_config_as_dict(self): |
| d = attrdict() |
| if hasattr(self, 'type'): |
| d.type = self.type |
| if hasattr(self, 'cxx_class'): |
| d.cxx_class = self.cxx_class |
| # Add the name and path of this object to be able to link to |
| # the stats |
| d.name = self.get_name() |
| d.path = self.path() |
| |
| for param in sorted(self._params.keys()): |
| value = self._values.get(param) |
| if value != None: |
| d[param] = value.config_value() |
| |
| for n in sorted(self._children.keys()): |
| child = self._children[n] |
| # Use the name of the attribute (and not get_name()) as |
| # the key in the JSON dictionary to capture the hierarchy |
| # in the Python code that assembled this system |
| d[n] = child.get_config_as_dict() |
| |
| for port_name in sorted(self._ports.keys()): |
| port = self._port_refs.get(port_name, None) |
| if port != None: |
| # Represent each port with a dictionary containing the |
| # prominent attributes |
| d[port_name] = port.get_config_as_dict() |
| |
| return d |
| |
| def getCCParams(self): |
| if self._ccParams: |
| return self._ccParams |
| |
| # Ensure that m5.internal.params is available. |
| import m5.internal.params |
| |
| cc_params_struct = getattr(m5.internal.params, '%sParams' % self.type) |
| cc_params = cc_params_struct() |
| cc_params.name = str(self) |
| |
| param_names = list(self._params.keys()) |
| param_names.sort() |
| for param in param_names: |
| value = self._values.get(param) |
| if value is None: |
| fatal("%s.%s without default or user set value", |
| self.path(), param) |
| |
| value = value.getValue() |
| if isinstance(self._params[param], VectorParamDesc): |
| assert isinstance(value, list) |
| vec = getattr(cc_params, param) |
| assert not len(vec) |
| # Some types are exposed as opaque types. They support |
| # the append operation unlike the automatically |
| # wrapped types. |
| if isinstance(vec, list): |
| setattr(cc_params, param, list(value)) |
| else: |
| for v in value: |
| getattr(cc_params, param).append(v) |
| else: |
| setattr(cc_params, param, value) |
| |
| port_names = list(self._ports.keys()) |
| port_names.sort() |
| for port_name in port_names: |
| port = self._port_refs.get(port_name, None) |
| if port != None: |
| port_count = len(port) |
| else: |
| port_count = 0 |
| setattr(cc_params, 'port_' + port_name + '_connection_count', |
| port_count) |
| self._ccParams = cc_params |
| return self._ccParams |
| |
| # Get C++ object corresponding to this object, calling C++ if |
| # necessary to construct it. Does *not* recursively create |
| # children. |
| def getCCObject(self): |
| if not self._ccObject: |
| # Make sure this object is in the configuration hierarchy |
| if not self._parent and not isRoot(self): |
| raise RuntimeError("Attempt to instantiate orphan node") |
| # Cycles in the configuration hierarchy are not supported. This |
| # will catch the resulting recursion and stop. |
| self._ccObject = -1 |
| if not self.abstract: |
| params = self.getCCParams() |
| self._ccObject = params.create() |
| elif self._ccObject == -1: |
| raise RuntimeError("%s: Cycle found in configuration hierarchy." \ |
| % self.path()) |
| return self._ccObject |
| |
| def descendants(self): |
| yield self |
| # The order of the dict is implementation dependent, so sort |
| # it based on the key (name) to ensure the order is the same |
| # on all hosts |
| for (name, child) in sorted(self._children.items()): |
| for obj in child.descendants(): |
| yield obj |
| |
| # Call C++ to create C++ object corresponding to this object |
| def createCCObject(self): |
| self.getCCParams() |
| self.getCCObject() # force creation |
| |
| def getValue(self): |
| return self.getCCObject() |
| |
| @cxxMethod(return_value_policy="reference") |
| def getPort(self, if_name, idx): |
| pass |
| |
| # Create C++ port connections corresponding to the connections in |
| # _port_refs |
| def connectPorts(self): |
| # Sort the ports based on their attribute name to ensure the |
| # order is the same on all hosts |
| for (attr, portRef) in sorted(self._port_refs.items()): |
| portRef.ccConnect() |
| |
| # Default function for generating the device structure. |
| # Can be overloaded by the inheriting class |
| def generateDeviceTree(self, state): |
| return # return without yielding anything |
| yield # make this function a (null) generator |
| |
| def recurseDeviceTree(self, state): |
| for child in self._children.values(): |
| for item in child: # For looping over SimObjectVectors |
| for dt in item.generateDeviceTree(state): |
| yield dt |
| |
| # On a separate method otherwise certain buggy Python versions |
| # would fail with: SyntaxError: unqualified exec is not allowed |
| # in function 'apply_config' |
| def _apply_config_get_dict(self): |
| return { |
| child_name: SimObjectCliWrapper( |
| iter(self._children[child_name])) |
| for child_name in self._children |
| } |
| |
| def apply_config(self, params): |
| """ |
| exec a list of Python code strings contained in params. |
| |
| The only exposed globals to those strings are the child |
| SimObjects of this node. |
| |
| This function is intended to allow users to modify SimObject |
| parameters from the command line with Python statements. |
| """ |
| d = self._apply_config_get_dict() |
| for param in params: |
| exec(param, d) |
| |
| def get_simobj(self, simobj_path): |
| """ |
| Get all sim objects that match a given string. |
| |
| The format is the same as that supported by SimObjectCliWrapper. |
| |
| :param simobj_path: Current state to be in. |
| :type simobj_path: str |
| """ |
| d = self._apply_config_get_dict() |
| return eval(simobj_path, d) |
| |
| # Function to provide to C++ so it can look up instances based on paths |
| def resolveSimObject(name): |
| obj = instanceDict[name] |
| return obj.getCCObject() |
| |
| def isSimObject(value): |
| return isinstance(value, SimObject) |
| |
| def isSimObjectClass(value): |
| return issubclass(value, SimObject) |
| |
| def isSimObjectVector(value): |
| return isinstance(value, SimObjectVector) |
| |
| def isSimObjectSequence(value): |
| if not isinstance(value, (list, tuple)) or len(value) == 0: |
| return False |
| |
| for val in value: |
| if not isNullPointer(val) and not isSimObject(val): |
| return False |
| |
| return True |
| |
| def isSimObjectOrSequence(value): |
| return isSimObject(value) or isSimObjectSequence(value) |
| |
| def isRoot(obj): |
| from m5.objects import Root |
| return obj and obj is Root.getInstance() |
| |
| def isSimObjectOrVector(value): |
| return isSimObject(value) or isSimObjectVector(value) |
| |
| def tryAsSimObjectOrVector(value): |
| if isSimObjectOrVector(value): |
| return value |
| if isSimObjectSequence(value): |
| return SimObjectVector(value) |
| return None |
| |
| def coerceSimObjectOrVector(value): |
| value = tryAsSimObjectOrVector(value) |
| if value is None: |
| raise TypeError("SimObject or SimObjectVector expected") |
| return value |
| |
| baseClasses = allClasses.copy() |
| baseInstances = instanceDict.copy() |
| |
| def clear(): |
| global allClasses, instanceDict, noCxxHeader |
| |
| allClasses = baseClasses.copy() |
| instanceDict = baseInstances.copy() |
| noCxxHeader = False |
| |
| # __all__ defines the list of symbols that get exported when |
| # 'from config import *' is invoked. Try to keep this reasonably |
| # short to avoid polluting other namespaces. |
| __all__ = [ |
| 'SimObject', |
| 'cxxMethod', |
| 'PyBindMethod', |
| 'PyBindProperty', |
| ] |