SimObject.py revision 14206
12651Ssaidi@eecs.umich.edu# Copyright (c) 2017-2019 ARM Limited
22651Ssaidi@eecs.umich.edu# All rights reserved.
32651Ssaidi@eecs.umich.edu#
42651Ssaidi@eecs.umich.edu# The license below extends only to copyright in the software and shall
52651Ssaidi@eecs.umich.edu# not be construed as granting a license to any other intellectual
62651Ssaidi@eecs.umich.edu# property including but not limited to intellectual property relating
72651Ssaidi@eecs.umich.edu# to a hardware implementation of the functionality of the software
82651Ssaidi@eecs.umich.edu# licensed hereunder.  You may use the software subject to the license
92651Ssaidi@eecs.umich.edu# terms below provided that you ensure that this notice is replicated
102651Ssaidi@eecs.umich.edu# unmodified and in its entirety in all distributions of the software,
112651Ssaidi@eecs.umich.edu# modified or unmodified, in source code or in binary form.
122651Ssaidi@eecs.umich.edu#
132651Ssaidi@eecs.umich.edu# Copyright (c) 2004-2006 The Regents of The University of Michigan
142651Ssaidi@eecs.umich.edu# Copyright (c) 2010-20013 Advanced Micro Devices, Inc.
152651Ssaidi@eecs.umich.edu# Copyright (c) 2013 Mark D. Hill and David A. Wood
162651Ssaidi@eecs.umich.edu# All rights reserved.
172651Ssaidi@eecs.umich.edu#
182651Ssaidi@eecs.umich.edu# Redistribution and use in source and binary forms, with or without
192651Ssaidi@eecs.umich.edu# modification, are permitted provided that the following conditions are
202651Ssaidi@eecs.umich.edu# met: redistributions of source code must retain the above copyright
212651Ssaidi@eecs.umich.edu# notice, this list of conditions and the following disclaimer;
222651Ssaidi@eecs.umich.edu# redistributions in binary form must reproduce the above copyright
232651Ssaidi@eecs.umich.edu# notice, this list of conditions and the following disclaimer in the
242651Ssaidi@eecs.umich.edu# documentation and/or other materials provided with the distribution;
252651Ssaidi@eecs.umich.edu# neither the name of the copyright holders nor the names of its
262651Ssaidi@eecs.umich.edu# contributors may be used to endorse or promote products derived from
272651Ssaidi@eecs.umich.edu# this software without specific prior written permission.
282651Ssaidi@eecs.umich.edu#
292651Ssaidi@eecs.umich.edu# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
302651Ssaidi@eecs.umich.edu# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
312651Ssaidi@eecs.umich.edu# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
322651Ssaidi@eecs.umich.edu# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
332651Ssaidi@eecs.umich.edu# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
342651Ssaidi@eecs.umich.edu# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
352651Ssaidi@eecs.umich.edu# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
362651Ssaidi@eecs.umich.edu# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
372680Sktlim@umich.edu# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
382651Ssaidi@eecs.umich.edu# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
392651Ssaidi@eecs.umich.edu# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
402680Sktlim@umich.edu#
412680Sktlim@umich.edu# Authors: Steve Reinhardt
422651Ssaidi@eecs.umich.edu#          Nathan Binkert
432651Ssaidi@eecs.umich.edu#          Andreas Hansson
442651Ssaidi@eecs.umich.edu#          Andreas Sandberg
452651Ssaidi@eecs.umich.edu
462651Ssaidi@eecs.umich.edufrom __future__ import print_function
472651Ssaidi@eecs.umich.edufrom __future__ import absolute_import
482651Ssaidi@eecs.umich.eduimport six
492651Ssaidi@eecs.umich.eduif six.PY3:
502651Ssaidi@eecs.umich.edu    long = int
512651Ssaidi@eecs.umich.edu
522651Ssaidi@eecs.umich.eduimport sys
532651Ssaidi@eecs.umich.edufrom types import FunctionType, MethodType, ModuleType
542651Ssaidi@eecs.umich.edufrom functools import wraps
552680Sktlim@umich.eduimport inspect
562680Sktlim@umich.edu
572651Ssaidi@eecs.umich.eduimport m5
582651Ssaidi@eecs.umich.edufrom m5.util import *
592680Sktlim@umich.edufrom m5.util.pybind import *
602680Sktlim@umich.edu# Use the pyfdt and not the helper class, because the fdthelper
612651Ssaidi@eecs.umich.edu# relies on the SimObject definition
622651Ssaidi@eecs.umich.edufrom m5.ext.pyfdt import pyfdt
632651Ssaidi@eecs.umich.edu
642651Ssaidi@eecs.umich.edu# Have to import params up top since Param is referenced on initial
652651Ssaidi@eecs.umich.edu# load (when SimObject class references Param to create a class
662680Sktlim@umich.edu# variable, the 'name' param)...
672680Sktlim@umich.edufrom m5.params import *
682680Sktlim@umich.edu# There are a few things we need that aren't in params.__all__ since
692651Ssaidi@eecs.umich.edu# normal users don't need them
702680Sktlim@umich.edufrom m5.params import ParamDesc, VectorParamDesc, \
712680Sktlim@umich.edu     isNullPointer, SimObjectVector, Port
722651Ssaidi@eecs.umich.edu
732651Ssaidi@eecs.umich.edufrom m5.proxy import *
742680Sktlim@umich.edufrom m5.proxy import isproxy
752651Ssaidi@eecs.umich.edu
762651Ssaidi@eecs.umich.edu#####################################################################
772651Ssaidi@eecs.umich.edu#
782651Ssaidi@eecs.umich.edu# M5 Python Configuration Utility
792651Ssaidi@eecs.umich.edu#
802651Ssaidi@eecs.umich.edu# The basic idea is to write simple Python programs that build Python
812680Sktlim@umich.edu# objects corresponding to M5 SimObjects for the desired simulation
822651Ssaidi@eecs.umich.edu# configuration.  For now, the Python emits a .ini file that can be
832680Sktlim@umich.edu# parsed by M5.  In the future, some tighter integration between M5
842651Ssaidi@eecs.umich.edu# and the Python interpreter may allow bypassing the .ini file.
852680Sktlim@umich.edu#
862651Ssaidi@eecs.umich.edu# Each SimObject class in M5 is represented by a Python class with the
872651Ssaidi@eecs.umich.edu# same name.  The Python inheritance tree mirrors the M5 C++ tree
882651Ssaidi@eecs.umich.edu# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
892651Ssaidi@eecs.umich.edu# SimObjects inherit from a single SimObject base class).  To specify
90# an instance of an M5 SimObject in a configuration, the user simply
91# instantiates the corresponding Python object.  The parameters for
92# that SimObject are given by assigning to attributes of the Python
93# object, either using keyword assignment in the constructor or in
94# separate assignment statements.  For example:
95#
96# cache = BaseCache(size='64KB')
97# cache.hit_latency = 3
98# cache.assoc = 8
99#
100# The magic lies in the mapping of the Python attributes for SimObject
101# classes to the actual SimObject parameter specifications.  This
102# allows parameter validity checking in the Python code.  Continuing
103# the example above, the statements "cache.blurfl=3" or
104# "cache.assoc='hello'" would both result in runtime errors in Python,
105# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
106# parameter requires an integer, respectively.  This magic is done
107# primarily by overriding the special __setattr__ method that controls
108# assignment to object attributes.
109#
110# Once a set of Python objects have been instantiated in a hierarchy,
111# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
112# will generate a .ini file.
113#
114#####################################################################
115
116# list of all SimObject classes
117allClasses = {}
118
119# dict to look up SimObjects based on path
120instanceDict = {}
121
122# Did any of the SimObjects lack a header file?
123noCxxHeader = False
124
125def public_value(key, value):
126    return key.startswith('_') or \
127               isinstance(value, (FunctionType, MethodType, ModuleType,
128                                  classmethod, type))
129
130def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header):
131    entry_class = 'CxxConfigDirectoryEntry_%s' % name
132    param_class = '%sCxxConfigParams' % name
133
134    code('#include "params/%s.hh"' % name)
135
136    if not is_header:
137        for param in simobj._params.values():
138            if isSimObjectClass(param.ptype):
139                code('#include "%s"' % param.ptype._value_dict['cxx_header'])
140                code('#include "params/%s.hh"' % param.ptype.__name__)
141            else:
142                param.ptype.cxx_ini_predecls(code)
143
144    if is_header:
145        member_prefix = ''
146        end_of_decl = ';'
147        code('#include "sim/cxx_config.hh"')
148        code()
149        code('class ${param_class} : public CxxConfigParams,'
150            ' public ${name}Params')
151        code('{')
152        code('  private:')
153        code.indent()
154        code('class DirectoryEntry : public CxxConfigDirectoryEntry')
155        code('{')
156        code('  public:')
157        code.indent()
158        code('DirectoryEntry();');
159        code()
160        code('CxxConfigParams *makeParamsObject() const')
161        code('{ return new ${param_class}; }')
162        code.dedent()
163        code('};')
164        code()
165        code.dedent()
166        code('  public:')
167        code.indent()
168    else:
169        member_prefix = '%s::' % param_class
170        end_of_decl = ''
171        code('#include "%s"' % simobj._value_dict['cxx_header'])
172        code('#include "base/str.hh"')
173        code('#include "cxx_config/${name}.hh"')
174
175        code()
176        code('${member_prefix}DirectoryEntry::DirectoryEntry()');
177        code('{')
178
179        def cxx_bool(b):
180            return 'true' if b else 'false'
181
182        code.indent()
183        for param in simobj._params.values():
184            is_vector = isinstance(param, m5.params.VectorParamDesc)
185            is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
186
187            code('parameters["%s"] = new ParamDesc("%s", %s, %s);' %
188                (param.name, param.name, cxx_bool(is_vector),
189                cxx_bool(is_simobj)));
190
191        for port in simobj._ports.values():
192            is_vector = isinstance(port, m5.params.VectorPort)
193            is_master = port.role == 'MASTER'
194
195            code('ports["%s"] = new PortDesc("%s", %s, %s);' %
196                (port.name, port.name, cxx_bool(is_vector),
197                cxx_bool(is_master)))
198
199        code.dedent()
200        code('}')
201        code()
202
203    code('bool ${member_prefix}setSimObject(const std::string &name,')
204    code('    SimObject *simObject)${end_of_decl}')
205
206    if not is_header:
207        code('{')
208        code.indent()
209        code('bool ret = true;')
210        code()
211        code('if (false) {')
212        for param in simobj._params.values():
213            is_vector = isinstance(param, m5.params.VectorParamDesc)
214            is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
215
216            if is_simobj and not is_vector:
217                code('} else if (name == "${{param.name}}") {')
218                code.indent()
219                code('this->${{param.name}} = '
220                    'dynamic_cast<${{param.ptype.cxx_type}}>(simObject);')
221                code('if (simObject && !this->${{param.name}})')
222                code('   ret = false;')
223                code.dedent()
224        code('} else {')
225        code('    ret = false;')
226        code('}')
227        code()
228        code('return ret;')
229        code.dedent()
230        code('}')
231
232    code()
233    code('bool ${member_prefix}setSimObjectVector('
234        'const std::string &name,')
235    code('    const std::vector<SimObject *> &simObjects)${end_of_decl}')
236
237    if not is_header:
238        code('{')
239        code.indent()
240        code('bool ret = true;')
241        code()
242        code('if (false) {')
243        for param in simobj._params.values():
244            is_vector = isinstance(param, m5.params.VectorParamDesc)
245            is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
246
247            if is_simobj and is_vector:
248                code('} else if (name == "${{param.name}}") {')
249                code.indent()
250                code('this->${{param.name}}.clear();')
251                code('for (auto i = simObjects.begin(); '
252                    'ret && i != simObjects.end(); i ++)')
253                code('{')
254                code.indent()
255                code('${{param.ptype.cxx_type}} object = '
256                    'dynamic_cast<${{param.ptype.cxx_type}}>(*i);')
257                code('if (*i && !object)')
258                code('    ret = false;')
259                code('else')
260                code('    this->${{param.name}}.push_back(object);')
261                code.dedent()
262                code('}')
263                code.dedent()
264        code('} else {')
265        code('    ret = false;')
266        code('}')
267        code()
268        code('return ret;')
269        code.dedent()
270        code('}')
271
272    code()
273    code('void ${member_prefix}setName(const std::string &name_)'
274        '${end_of_decl}')
275
276    if not is_header:
277        code('{')
278        code.indent()
279        code('this->name = name_;')
280        code.dedent()
281        code('}')
282
283    if is_header:
284        code('const std::string &${member_prefix}getName()')
285        code('{ return this->name; }')
286
287    code()
288    code('bool ${member_prefix}setParam(const std::string &name,')
289    code('    const std::string &value, const Flags flags)${end_of_decl}')
290
291    if not is_header:
292        code('{')
293        code.indent()
294        code('bool ret = true;')
295        code()
296        code('if (false) {')
297        for param in simobj._params.values():
298            is_vector = isinstance(param, m5.params.VectorParamDesc)
299            is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
300
301            if not is_simobj and not is_vector:
302                code('} else if (name == "${{param.name}}") {')
303                code.indent()
304                param.ptype.cxx_ini_parse(code,
305                    'value', 'this->%s' % param.name, 'ret =')
306                code.dedent()
307        code('} else {')
308        code('    ret = false;')
309        code('}')
310        code()
311        code('return ret;')
312        code.dedent()
313        code('}')
314
315    code()
316    code('bool ${member_prefix}setParamVector('
317        'const std::string &name,')
318    code('    const std::vector<std::string> &values,')
319    code('    const Flags flags)${end_of_decl}')
320
321    if not is_header:
322        code('{')
323        code.indent()
324        code('bool ret = true;')
325        code()
326        code('if (false) {')
327        for param in simobj._params.values():
328            is_vector = isinstance(param, m5.params.VectorParamDesc)
329            is_simobj = issubclass(param.ptype, m5.SimObject.SimObject)
330
331            if not is_simobj and is_vector:
332                code('} else if (name == "${{param.name}}") {')
333                code.indent()
334                code('${{param.name}}.clear();')
335                code('for (auto i = values.begin(); '
336                    'ret && i != values.end(); i ++)')
337                code('{')
338                code.indent()
339                code('${{param.ptype.cxx_type}} elem;')
340                param.ptype.cxx_ini_parse(code,
341                    '*i', 'elem', 'ret =')
342                code('if (ret)')
343                code('    this->${{param.name}}.push_back(elem);')
344                code.dedent()
345                code('}')
346                code.dedent()
347        code('} else {')
348        code('    ret = false;')
349        code('}')
350        code()
351        code('return ret;')
352        code.dedent()
353        code('}')
354
355    code()
356    code('bool ${member_prefix}setPortConnectionCount('
357        'const std::string &name,')
358    code('    unsigned int count)${end_of_decl}')
359
360    if not is_header:
361        code('{')
362        code.indent()
363        code('bool ret = true;')
364        code()
365        code('if (false)')
366        code('    ;')
367        for port in simobj._ports.values():
368            code('else if (name == "${{port.name}}")')
369            code('    this->port_${{port.name}}_connection_count = count;')
370        code('else')
371        code('    ret = false;')
372        code()
373        code('return ret;')
374        code.dedent()
375        code('}')
376
377    code()
378    code('SimObject *${member_prefix}simObjectCreate()${end_of_decl}')
379
380    if not is_header:
381        code('{')
382        if hasattr(simobj, 'abstract') and simobj.abstract:
383            code('    return NULL;')
384        else:
385            code('    return this->create();')
386        code('}')
387
388    if is_header:
389        code()
390        code('static CxxConfigDirectoryEntry'
391            ' *${member_prefix}makeDirectoryEntry()')
392        code('{ return new DirectoryEntry; }')
393
394    if is_header:
395        code.dedent()
396        code('};')
397
398# The metaclass for SimObject.  This class controls how new classes
399# that derive from SimObject are instantiated, and provides inherited
400# class behavior (just like a class controls how instances of that
401# class are instantiated, and provides inherited instance behavior).
402class MetaSimObject(type):
403    # Attributes that can be set only at initialization time
404    init_keywords = {
405        'abstract' : bool,
406        'cxx_class' : str,
407        'cxx_type' : str,
408        'cxx_header' : str,
409        'type' : str,
410        'cxx_base' : (str, type(None)),
411        'cxx_extra_bases' : list,
412        'cxx_exports' : list,
413        'cxx_param_exports' : list,
414        'cxx_template_params' : list,
415    }
416    # Attributes that can be set any time
417    keywords = { 'check' : FunctionType }
418
419    # __new__ is called before __init__, and is where the statements
420    # in the body of the class definition get loaded into the class's
421    # __dict__.  We intercept this to filter out parameter & port assignments
422    # and only allow "private" attributes to be passed to the base
423    # __new__ (starting with underscore).
424    def __new__(mcls, name, bases, dict):
425        assert name not in allClasses, "SimObject %s already present" % name
426
427        # Copy "private" attributes, functions, and classes to the
428        # official dict.  Everything else goes in _init_dict to be
429        # filtered in __init__.
430        cls_dict = {}
431        value_dict = {}
432        cxx_exports = []
433        for key,val in dict.items():
434            try:
435                cxx_exports.append(getattr(val, "__pybind"))
436            except AttributeError:
437                pass
438
439            if public_value(key, val):
440                cls_dict[key] = val
441            else:
442                # must be a param/port setting
443                value_dict[key] = val
444        if 'abstract' not in value_dict:
445            value_dict['abstract'] = False
446        if 'cxx_extra_bases' not in value_dict:
447            value_dict['cxx_extra_bases'] = []
448        if 'cxx_exports' not in value_dict:
449            value_dict['cxx_exports'] = cxx_exports
450        else:
451            value_dict['cxx_exports'] += cxx_exports
452        if 'cxx_param_exports' not in value_dict:
453            value_dict['cxx_param_exports'] = []
454        if 'cxx_template_params' not in value_dict:
455            value_dict['cxx_template_params'] = []
456        cls_dict['_value_dict'] = value_dict
457        cls = super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
458        if 'type' in value_dict:
459            allClasses[name] = cls
460        return cls
461
462    # subclass initialization
463    def __init__(cls, name, bases, dict):
464        # calls type.__init__()... I think that's a no-op, but leave
465        # it here just in case it's not.
466        super(MetaSimObject, cls).__init__(name, bases, dict)
467
468        # initialize required attributes
469
470        # class-only attributes
471        cls._params = multidict() # param descriptions
472        cls._ports = multidict()  # port descriptions
473
474        # class or instance attributes
475        cls._values = multidict()   # param values
476        cls._hr_values = multidict() # human readable param values
477        cls._children = multidict() # SimObject children
478        cls._port_refs = multidict() # port ref objects
479        cls._instantiated = False # really instantiated, cloned, or subclassed
480
481        # We don't support multiple inheritance of sim objects.  If you want
482        # to, you must fix multidict to deal with it properly. Non sim-objects
483        # are ok, though
484        bTotal = 0
485        for c in bases:
486            if isinstance(c, MetaSimObject):
487                bTotal += 1
488            if bTotal > 1:
489                raise TypeError(
490                      "SimObjects do not support multiple inheritance")
491
492        base = bases[0]
493
494        # Set up general inheritance via multidicts.  A subclass will
495        # inherit all its settings from the base class.  The only time
496        # the following is not true is when we define the SimObject
497        # class itself (in which case the multidicts have no parent).
498        if isinstance(base, MetaSimObject):
499            cls._base = base
500            cls._params.parent = base._params
501            cls._ports.parent = base._ports
502            cls._values.parent = base._values
503            cls._hr_values.parent = base._hr_values
504            cls._children.parent = base._children
505            cls._port_refs.parent = base._port_refs
506            # mark base as having been subclassed
507            base._instantiated = True
508        else:
509            cls._base = None
510
511        # default keyword values
512        if 'type' in cls._value_dict:
513            if 'cxx_class' not in cls._value_dict:
514                cls._value_dict['cxx_class'] = cls._value_dict['type']
515
516            cls._value_dict['cxx_type'] = '%s *' % cls._value_dict['cxx_class']
517
518            if 'cxx_header' not in cls._value_dict:
519                global noCxxHeader
520                noCxxHeader = True
521                warn("No header file specified for SimObject: %s", name)
522
523        # Now process the _value_dict items.  They could be defining
524        # new (or overriding existing) parameters or ports, setting
525        # class keywords (e.g., 'abstract'), or setting parameter
526        # values or port bindings.  The first 3 can only be set when
527        # the class is defined, so we handle them here.  The others
528        # can be set later too, so just emulate that by calling
529        # setattr().
530        for key,val in cls._value_dict.items():
531            # param descriptions
532            if isinstance(val, ParamDesc):
533                cls._new_param(key, val)
534
535            # port objects
536            elif isinstance(val, Port):
537                cls._new_port(key, val)
538
539            # init-time-only keywords
540            elif key in cls.init_keywords:
541                cls._set_keyword(key, val, cls.init_keywords[key])
542
543            # default: use normal path (ends up in __setattr__)
544            else:
545                setattr(cls, key, val)
546
547    def _set_keyword(cls, keyword, val, kwtype):
548        if not isinstance(val, kwtype):
549            raise TypeError('keyword %s has bad type %s (expecting %s)' % \
550                  (keyword, type(val), kwtype))
551        if isinstance(val, FunctionType):
552            val = classmethod(val)
553        type.__setattr__(cls, keyword, val)
554
555    def _new_param(cls, name, pdesc):
556        # each param desc should be uniquely assigned to one variable
557        assert(not hasattr(pdesc, 'name'))
558        pdesc.name = name
559        cls._params[name] = pdesc
560        if hasattr(pdesc, 'default'):
561            cls._set_param(name, pdesc.default, pdesc)
562
563    def _set_param(cls, name, value, param):
564        assert(param.name == name)
565        try:
566            hr_value = value
567            value = param.convert(value)
568        except Exception as e:
569            msg = "%s\nError setting param %s.%s to %s\n" % \
570                  (e, cls.__name__, name, value)
571            e.args = (msg, )
572            raise
573        cls._values[name] = value
574        # if param value is a SimObject, make it a child too, so that
575        # it gets cloned properly when the class is instantiated
576        if isSimObjectOrVector(value) and not value.has_parent():
577            cls._add_cls_child(name, value)
578        # update human-readable values of the param if it has a literal
579        # value and is not an object or proxy.
580        if not (isSimObjectOrVector(value) or\
581                isinstance(value, m5.proxy.BaseProxy)):
582            cls._hr_values[name] = hr_value
583
584    def _add_cls_child(cls, name, child):
585        # It's a little funky to have a class as a parent, but these
586        # objects should never be instantiated (only cloned, which
587        # clears the parent pointer), and this makes it clear that the
588        # object is not an orphan and can provide better error
589        # messages.
590        child.set_parent(cls, name)
591        if not isNullPointer(child):
592            cls._children[name] = child
593
594    def _new_port(cls, name, port):
595        # each port should be uniquely assigned to one variable
596        assert(not hasattr(port, 'name'))
597        port.name = name
598        cls._ports[name] = port
599
600    # same as _get_port_ref, effectively, but for classes
601    def _cls_get_port_ref(cls, attr):
602        # Return reference that can be assigned to another port
603        # via __setattr__.  There is only ever one reference
604        # object per port, but we create them lazily here.
605        ref = cls._port_refs.get(attr)
606        if not ref:
607            ref = cls._ports[attr].makeRef(cls)
608            cls._port_refs[attr] = ref
609        return ref
610
611    # Set attribute (called on foo.attr = value when foo is an
612    # instance of class cls).
613    def __setattr__(cls, attr, value):
614        # normal processing for private attributes
615        if public_value(attr, value):
616            type.__setattr__(cls, attr, value)
617            return
618
619        if attr in cls.keywords:
620            cls._set_keyword(attr, value, cls.keywords[attr])
621            return
622
623        if attr in cls._ports:
624            cls._cls_get_port_ref(attr).connect(value)
625            return
626
627        if isSimObjectOrSequence(value) and cls._instantiated:
628            raise RuntimeError(
629                  "cannot set SimObject parameter '%s' after\n" \
630                  "    class %s has been instantiated or subclassed" \
631                  % (attr, cls.__name__))
632
633        # check for param
634        param = cls._params.get(attr)
635        if param:
636            cls._set_param(attr, value, param)
637            return
638
639        if isSimObjectOrSequence(value):
640            # If RHS is a SimObject, it's an implicit child assignment.
641            cls._add_cls_child(attr, coerceSimObjectOrVector(value))
642            return
643
644        # no valid assignment... raise exception
645        raise AttributeError(
646              "Class %s has no parameter \'%s\'" % (cls.__name__, attr))
647
648    def __getattr__(cls, attr):
649        if attr == 'cxx_class_path':
650            return cls.cxx_class.split('::')
651
652        if attr == 'cxx_class_name':
653            return cls.cxx_class_path[-1]
654
655        if attr == 'cxx_namespaces':
656            return cls.cxx_class_path[:-1]
657
658        if attr == 'pybind_class':
659            return  '_COLONS_'.join(cls.cxx_class_path)
660
661        if attr in cls._values:
662            return cls._values[attr]
663
664        if attr in cls._children:
665            return cls._children[attr]
666
667        try:
668            return getattr(cls.getCCClass(), attr)
669        except AttributeError:
670            raise AttributeError(
671                "object '%s' has no attribute '%s'" % (cls.__name__, attr))
672
673    def __str__(cls):
674        return cls.__name__
675
676    def getCCClass(cls):
677        return getattr(m5.internal.params, cls.pybind_class)
678
679    # See ParamValue.cxx_predecls for description.
680    def cxx_predecls(cls, code):
681        code('#include "params/$cls.hh"')
682
683    def pybind_predecls(cls, code):
684        code('#include "${{cls.cxx_header}}"')
685
686    def pybind_decl(cls, code):
687        py_class_name = cls.pybind_class
688
689        # The 'local' attribute restricts us to the params declared in
690        # the object itself, not including inherited params (which
691        # will also be inherited from the base class's param struct
692        # here). Sort the params based on their key
693        params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
694        ports = cls._ports.local
695
696        code('''#include "pybind11/pybind11.h"
697#include "pybind11/stl.h"
698
699#include "params/$cls.hh"
700#include "python/pybind11/core.hh"
701#include "sim/init.hh"
702#include "sim/sim_object.hh"
703
704#include "${{cls.cxx_header}}"
705
706''')
707
708        for param in params:
709            param.pybind_predecls(code)
710
711        code('''namespace py = pybind11;
712
713static void
714module_init(py::module &m_internal)
715{
716    py::module m = m_internal.def_submodule("param_${cls}");
717''')
718        code.indent()
719        if cls._base:
720            code('py::class_<${cls}Params, ${{cls._base.type}}Params, ' \
721                 'std::unique_ptr<${{cls}}Params, py::nodelete>>(' \
722                 'm, "${cls}Params")')
723        else:
724            code('py::class_<${cls}Params, ' \
725                 'std::unique_ptr<${cls}Params, py::nodelete>>(' \
726                 'm, "${cls}Params")')
727
728        code.indent()
729        if not hasattr(cls, 'abstract') or not cls.abstract:
730            code('.def(py::init<>())')
731            code('.def("create", &${cls}Params::create)')
732
733        param_exports = cls.cxx_param_exports + [
734            PyBindProperty(k)
735            for k, v in sorted(cls._params.local.items())
736        ] + [
737            PyBindProperty("port_%s_connection_count" % port.name)
738            for port in ports.values()
739        ]
740        for exp in param_exports:
741            exp.export(code, "%sParams" % cls)
742
743        code(';')
744        code()
745        code.dedent()
746
747        bases = []
748        if 'cxx_base' in cls._value_dict:
749            # If the c++ base class implied by python inheritance was
750            # overridden, use that value.
751            if cls.cxx_base:
752                bases.append(cls.cxx_base)
753        elif cls._base:
754            # If not and if there was a SimObject base, use its c++ class
755            # as this class' base.
756            bases.append(cls._base.cxx_class)
757        # Add in any extra bases that were requested.
758        bases.extend(cls.cxx_extra_bases)
759
760        if bases:
761            base_str = ", ".join(bases)
762            code('py::class_<${{cls.cxx_class}}, ${base_str}, ' \
763                 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
764                 'm, "${py_class_name}")')
765        else:
766            code('py::class_<${{cls.cxx_class}}, ' \
767                 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
768                 'm, "${py_class_name}")')
769        code.indent()
770        for exp in cls.cxx_exports:
771            exp.export(code, cls.cxx_class)
772        code(';')
773        code.dedent()
774        code()
775        code.dedent()
776        code('}')
777        code()
778        code('static EmbeddedPyBind embed_obj("${0}", module_init, "${1}");',
779             cls, cls._base.type if cls._base else "")
780
781    _warned_about_nested_templates = False
782
783    # Generate the C++ declaration (.hh file) for this SimObject's
784    # param struct.  Called from src/SConscript.
785    def cxx_param_decl(cls, code):
786        # The 'local' attribute restricts us to the params declared in
787        # the object itself, not including inherited params (which
788        # will also be inherited from the base class's param struct
789        # here). Sort the params based on their key
790        params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
791        ports = cls._ports.local
792        try:
793            ptypes = [p.ptype for p in params]
794        except:
795            print(cls, p, p.ptype_str)
796            print(params)
797            raise
798
799        class CxxClass(object):
800            def __init__(self, sig, template_params=[]):
801                # Split the signature into its constituent parts. This could
802                # potentially be done with regular expressions, but
803                # it's simple enough to pick appart a class signature
804                # manually.
805                parts = sig.split('<', 1)
806                base = parts[0]
807                t_args = []
808                if len(parts) > 1:
809                    # The signature had template arguments.
810                    text = parts[1].rstrip(' \t\n>')
811                    arg = ''
812                    # Keep track of nesting to avoid splitting on ","s embedded
813                    # in the arguments themselves.
814                    depth = 0
815                    for c in text:
816                        if c == '<':
817                            depth = depth + 1
818                            if depth > 0 and not \
819                                    self._warned_about_nested_templates:
820                                self._warned_about_nested_templates = True
821                                print('Nested template argument in cxx_class.'
822                                      ' This feature is largely untested and '
823                                      ' may not work.')
824                        elif c == '>':
825                            depth = depth - 1
826                        elif c == ',' and depth == 0:
827                            t_args.append(arg.strip())
828                            arg = ''
829                        else:
830                            arg = arg + c
831                    if arg:
832                        t_args.append(arg.strip())
833                # Split the non-template part on :: boundaries.
834                class_path = base.split('::')
835
836                # The namespaces are everything except the last part of the
837                # class path.
838                self.namespaces = class_path[:-1]
839                # And the class name is the last part.
840                self.name = class_path[-1]
841
842                self.template_params = template_params
843                self.template_arguments = []
844                # Iterate through the template arguments and their values. This
845                # will likely break if parameter packs are used.
846                for arg, param in zip(t_args, template_params):
847                    type_keys = ('class', 'typename')
848                    # If a parameter is a type, parse it recursively. Otherwise
849                    # assume it's a constant, and store it verbatim.
850                    if any(param.strip().startswith(kw) for kw in type_keys):
851                        self.template_arguments.append(CxxClass(arg))
852                    else:
853                        self.template_arguments.append(arg)
854
855            def declare(self, code):
856                # First declare any template argument types.
857                for arg in self.template_arguments:
858                    if isinstance(arg, CxxClass):
859                        arg.declare(code)
860                # Re-open the target namespace.
861                for ns in self.namespaces:
862                    code('namespace $ns {')
863                # If this is a class template...
864                if self.template_params:
865                    code('template <${{", ".join(self.template_params)}}>')
866                # The actual class declaration.
867                code('class ${{self.name}};')
868                # Close the target namespaces.
869                for ns in reversed(self.namespaces):
870                    code('} // namespace $ns')
871
872        code('''\
873#ifndef __PARAMS__${cls}__
874#define __PARAMS__${cls}__
875
876''')
877
878
879        # The base SimObject has a couple of params that get
880        # automatically set from Python without being declared through
881        # the normal Param mechanism; we slip them in here (needed
882        # predecls now, actual declarations below)
883        if cls == SimObject:
884            code('''#include <string>''')
885
886        cxx_class = CxxClass(cls._value_dict['cxx_class'],
887                             cls._value_dict['cxx_template_params'])
888
889        # A forward class declaration is sufficient since we are just
890        # declaring a pointer.
891        cxx_class.declare(code)
892
893        for param in params:
894            param.cxx_predecls(code)
895        for port in ports.values():
896            port.cxx_predecls(code)
897        code()
898
899        if cls._base:
900            code('#include "params/${{cls._base.type}}.hh"')
901            code()
902
903        for ptype in ptypes:
904            if issubclass(ptype, Enum):
905                code('#include "enums/${{ptype.__name__}}.hh"')
906                code()
907
908        # now generate the actual param struct
909        code("struct ${cls}Params")
910        if cls._base:
911            code("    : public ${{cls._base.type}}Params")
912        code("{")
913        if not hasattr(cls, 'abstract') or not cls.abstract:
914            if 'type' in cls.__dict__:
915                code("    ${{cls.cxx_type}} create();")
916
917        code.indent()
918        if cls == SimObject:
919            code('''
920    SimObjectParams() {}
921    virtual ~SimObjectParams() {}
922
923    std::string name;
924            ''')
925
926        for param in params:
927            param.cxx_decl(code)
928        for port in ports.values():
929            port.cxx_decl(code)
930
931        code.dedent()
932        code('};')
933
934        code()
935        code('#endif // __PARAMS__${cls}__')
936        return code
937
938    # Generate the C++ declaration/definition files for this SimObject's
939    # param struct to allow C++ initialisation
940    def cxx_config_param_file(cls, code, is_header):
941        createCxxConfigDirectoryEntryFile(code, cls.__name__, cls, is_header)
942        return code
943
944# This *temporary* definition is required to support calls from the
945# SimObject class definition to the MetaSimObject methods (in
946# particular _set_param, which gets called for parameters with default
947# values defined on the SimObject class itself).  It will get
948# overridden by the permanent definition (which requires that
949# SimObject be defined) lower in this file.
950def isSimObjectOrVector(value):
951    return False
952
953def cxxMethod(*args, **kwargs):
954    """Decorator to export C++ functions to Python"""
955
956    def decorate(func):
957        name = func.__name__
958        override = kwargs.get("override", False)
959        cxx_name = kwargs.get("cxx_name", name)
960        return_value_policy = kwargs.get("return_value_policy", None)
961        static = kwargs.get("static", False)
962
963        args, varargs, keywords, defaults = inspect.getargspec(func)
964        if varargs or keywords:
965            raise ValueError("Wrapped methods must not contain variable " \
966                             "arguments")
967
968        # Create tuples of (argument, default)
969        if defaults:
970            args = args[:-len(defaults)] + \
971                   list(zip(args[-len(defaults):], defaults))
972        # Don't include self in the argument list to PyBind
973        args = args[1:]
974
975
976        @wraps(func)
977        def cxx_call(self, *args, **kwargs):
978            ccobj = self.getCCClass() if static else self.getCCObject()
979            return getattr(ccobj, name)(*args, **kwargs)
980
981        @wraps(func)
982        def py_call(self, *args, **kwargs):
983            return func(self, *args, **kwargs)
984
985        f = py_call if override else cxx_call
986        f.__pybind = PyBindMethod(name, cxx_name=cxx_name, args=args,
987                                  return_value_policy=return_value_policy,
988                                  static=static)
989
990        return f
991
992    if len(args) == 0:
993        return decorate
994    elif len(args) == 1 and len(kwargs) == 0:
995        return decorate(*args)
996    else:
997        raise TypeError("One argument and no kwargs, or only kwargs expected")
998
999# This class holds information about each simobject parameter
1000# that should be displayed on the command line for use in the
1001# configuration system.
1002class ParamInfo(object):
1003  def __init__(self, type, desc, type_str, example, default_val, access_str):
1004    self.type = type
1005    self.desc = desc
1006    self.type_str = type_str
1007    self.example_str = example
1008    self.default_val = default_val
1009    # The string representation used to access this param through python.
1010    # The method to access this parameter presented on the command line may
1011    # be different, so this needs to be stored for later use.
1012    self.access_str = access_str
1013    self.created = True
1014
1015  # Make it so we can only set attributes at initialization time
1016  # and effectively make this a const object.
1017  def __setattr__(self, name, value):
1018    if not "created" in self.__dict__:
1019      self.__dict__[name] = value
1020
1021class SimObjectCliWrapperException(Exception):
1022    def __init__(self, message):
1023        super(Exception, self).__init__(message)
1024
1025class SimObjectCliWrapper(object):
1026    """
1027    Wrapper class to restrict operations that may be done
1028    from the command line on SimObjects.
1029
1030    Only parameters may be set, and only children may be accessed.
1031
1032    Slicing allows for multiple simultaneous assignment of items in
1033    one statement.
1034    """
1035
1036    def __init__(self, sim_objects):
1037        self.__dict__['_sim_objects'] = list(sim_objects)
1038
1039    def __getattr__(self, key):
1040        return SimObjectCliWrapper(sim_object._children[key]
1041                for sim_object in self._sim_objects)
1042
1043    def __setattr__(self, key, val):
1044        for sim_object in self._sim_objects:
1045            if key in sim_object._params:
1046                if sim_object._params[key].isCmdLineSettable():
1047                    setattr(sim_object, key, val)
1048                else:
1049                    raise SimObjectCliWrapperException(
1050                            'tried to set or unsettable' \
1051                            'object parameter: ' + key)
1052            else:
1053                raise SimObjectCliWrapperException(
1054                            'tried to set or access non-existent' \
1055                            'object parameter: ' + key)
1056
1057    def __getitem__(self, idx):
1058        """
1059        Extends the list() semantics to also allow tuples,
1060        for example object[1, 3] selects items 1 and 3.
1061        """
1062        out = []
1063        if isinstance(idx, tuple):
1064            for t in idx:
1065                out.extend(self[t]._sim_objects)
1066        else:
1067            if isinstance(idx, int):
1068                _range = range(idx, idx + 1)
1069            elif not isinstance(idx, slice):
1070                raise SimObjectCliWrapperException( \
1071                        'invalid index type: ' + repr(idx))
1072            for sim_object in self._sim_objects:
1073                if isinstance(idx, slice):
1074                    _range = range(*idx.indices(len(sim_object)))
1075                out.extend(sim_object[i] for i in _range)
1076        return SimObjectCliWrapper(out)
1077
1078# The SimObject class is the root of the special hierarchy.  Most of
1079# the code in this class deals with the configuration hierarchy itself
1080# (parent/child node relationships).
1081class SimObject(object):
1082    # Specify metaclass.  Any class inheriting from SimObject will
1083    # get this metaclass.
1084    __metaclass__ = MetaSimObject
1085    type = 'SimObject'
1086    abstract = True
1087
1088    cxx_header = "sim/sim_object.hh"
1089    cxx_extra_bases = [ "Drainable", "Serializable", "Stats::Group" ]
1090    eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index")
1091
1092    cxx_exports = [
1093        PyBindMethod("init"),
1094        PyBindMethod("initState"),
1095        PyBindMethod("memInvalidate"),
1096        PyBindMethod("memWriteback"),
1097        PyBindMethod("regProbePoints"),
1098        PyBindMethod("regProbeListeners"),
1099        PyBindMethod("startup"),
1100    ]
1101
1102    cxx_param_exports = [
1103        PyBindProperty("name"),
1104    ]
1105
1106    @cxxMethod
1107    def loadState(self, cp):
1108        """Load SimObject state from a checkpoint"""
1109        pass
1110
1111    # Returns a dict of all the option strings that can be
1112    # generated as command line options for this simobject instance
1113    # by tracing all reachable params in the top level instance and
1114    # any children it contains.
1115    def enumerateParams(self, flags_dict = {},
1116                        cmd_line_str = "", access_str = ""):
1117        if hasattr(self, "_paramEnumed"):
1118            print("Cycle detected enumerating params")
1119        else:
1120            self._paramEnumed = True
1121            # Scan the children first to pick up all the objects in this SimObj
1122            for keys in self._children:
1123                child = self._children[keys]
1124                next_cmdline_str = cmd_line_str + keys
1125                next_access_str = access_str + keys
1126                if not isSimObjectVector(child):
1127                    next_cmdline_str = next_cmdline_str + "."
1128                    next_access_str = next_access_str + "."
1129                flags_dict = child.enumerateParams(flags_dict,
1130                                                   next_cmdline_str,
1131                                                   next_access_str)
1132
1133            # Go through the simple params in the simobject in this level
1134            # of the simobject hierarchy and save information about the
1135            # parameter to be used for generating and processing command line
1136            # options to the simulator to set these parameters.
1137            for keys,values in self._params.items():
1138                if values.isCmdLineSettable():
1139                    type_str = ''
1140                    ex_str = values.example_str()
1141                    ptype = None
1142                    if isinstance(values, VectorParamDesc):
1143                        type_str = 'Vector_%s' % values.ptype_str
1144                        ptype = values
1145                    else:
1146                        type_str = '%s' % values.ptype_str
1147                        ptype = values.ptype
1148
1149                    if keys in self._hr_values\
1150                       and keys in self._values\
1151                       and not isinstance(self._values[keys],
1152                                          m5.proxy.BaseProxy):
1153                        cmd_str = cmd_line_str + keys
1154                        acc_str = access_str + keys
1155                        flags_dict[cmd_str] = ParamInfo(ptype,
1156                                    self._params[keys].desc, type_str, ex_str,
1157                                    values.pretty_print(self._hr_values[keys]),
1158                                    acc_str)
1159                    elif not keys in self._hr_values\
1160                         and not keys in self._values:
1161                        # Empty param
1162                        cmd_str = cmd_line_str + keys
1163                        acc_str = access_str + keys
1164                        flags_dict[cmd_str] = ParamInfo(ptype,
1165                                    self._params[keys].desc,
1166                                    type_str, ex_str, '', acc_str)
1167
1168        return flags_dict
1169
1170    # Initialize new instance.  For objects with SimObject-valued
1171    # children, we need to recursively clone the classes represented
1172    # by those param values as well in a consistent "deep copy"-style
1173    # fashion.  That is, we want to make sure that each instance is
1174    # cloned only once, and that if there are multiple references to
1175    # the same original object, we end up with the corresponding
1176    # cloned references all pointing to the same cloned instance.
1177    def __init__(self, **kwargs):
1178        ancestor = kwargs.get('_ancestor')
1179        memo_dict = kwargs.get('_memo')
1180        if memo_dict is None:
1181            # prepare to memoize any recursively instantiated objects
1182            memo_dict = {}
1183        elif ancestor:
1184            # memoize me now to avoid problems with recursive calls
1185            memo_dict[ancestor] = self
1186
1187        if not ancestor:
1188            ancestor = self.__class__
1189        ancestor._instantiated = True
1190
1191        # initialize required attributes
1192        self._parent = None
1193        self._name = None
1194        self._ccObject = None  # pointer to C++ object
1195        self._ccParams = None
1196        self._instantiated = False # really "cloned"
1197
1198        # Clone children specified at class level.  No need for a
1199        # multidict here since we will be cloning everything.
1200        # Do children before parameter values so that children that
1201        # are also param values get cloned properly.
1202        self._children = {}
1203        for key,val in ancestor._children.items():
1204            self.add_child(key, val(_memo=memo_dict))
1205
1206        # Inherit parameter values from class using multidict so
1207        # individual value settings can be overridden but we still
1208        # inherit late changes to non-overridden class values.
1209        self._values = multidict(ancestor._values)
1210        self._hr_values = multidict(ancestor._hr_values)
1211        # clone SimObject-valued parameters
1212        for key,val in ancestor._values.items():
1213            val = tryAsSimObjectOrVector(val)
1214            if val is not None:
1215                self._values[key] = val(_memo=memo_dict)
1216
1217        # clone port references.  no need to use a multidict here
1218        # since we will be creating new references for all ports.
1219        self._port_refs = {}
1220        for key,val in ancestor._port_refs.items():
1221            self._port_refs[key] = val.clone(self, memo_dict)
1222        # apply attribute assignments from keyword args, if any
1223        for key,val in kwargs.items():
1224            setattr(self, key, val)
1225
1226    # "Clone" the current instance by creating another instance of
1227    # this instance's class, but that inherits its parameter values
1228    # and port mappings from the current instance.  If we're in a
1229    # "deep copy" recursive clone, check the _memo dict to see if
1230    # we've already cloned this instance.
1231    def __call__(self, **kwargs):
1232        memo_dict = kwargs.get('_memo')
1233        if memo_dict is None:
1234            # no memo_dict: must be top-level clone operation.
1235            # this is only allowed at the root of a hierarchy
1236            if self._parent:
1237                raise RuntimeError("attempt to clone object %s " \
1238                      "not at the root of a tree (parent = %s)" \
1239                      % (self, self._parent))
1240            # create a new dict and use that.
1241            memo_dict = {}
1242            kwargs['_memo'] = memo_dict
1243        elif self in memo_dict:
1244            # clone already done & memoized
1245            return memo_dict[self]
1246        return self.__class__(_ancestor = self, **kwargs)
1247
1248    def _get_port_ref(self, attr):
1249        # Return reference that can be assigned to another port
1250        # via __setattr__.  There is only ever one reference
1251        # object per port, but we create them lazily here.
1252        ref = self._port_refs.get(attr)
1253        if ref == None:
1254            ref = self._ports[attr].makeRef(self)
1255            self._port_refs[attr] = ref
1256        return ref
1257
1258    def __getattr__(self, attr):
1259        if attr in self._ports:
1260            return self._get_port_ref(attr)
1261
1262        if attr in self._values:
1263            return self._values[attr]
1264
1265        if attr in self._children:
1266            return self._children[attr]
1267
1268        # If the attribute exists on the C++ object, transparently
1269        # forward the reference there.  This is typically used for
1270        # methods exported to Python (e.g., init(), and startup())
1271        if self._ccObject and hasattr(self._ccObject, attr):
1272            return getattr(self._ccObject, attr)
1273
1274        err_string = "object '%s' has no attribute '%s'" \
1275              % (self.__class__.__name__, attr)
1276
1277        if not self._ccObject:
1278            err_string += "\n  (C++ object is not yet constructed," \
1279                          " so wrapped C++ methods are unavailable.)"
1280
1281        raise AttributeError(err_string)
1282
1283    # Set attribute (called on foo.attr = value when foo is an
1284    # instance of class cls).
1285    def __setattr__(self, attr, value):
1286        # normal processing for private attributes
1287        if attr.startswith('_'):
1288            object.__setattr__(self, attr, value)
1289            return
1290
1291        if attr in self._ports:
1292            # set up port connection
1293            self._get_port_ref(attr).connect(value)
1294            return
1295
1296        param = self._params.get(attr)
1297        if param:
1298            try:
1299                hr_value = value
1300                value = param.convert(value)
1301            except Exception as e:
1302                msg = "%s\nError setting param %s.%s to %s\n" % \
1303                      (e, self.__class__.__name__, attr, value)
1304                e.args = (msg, )
1305                raise
1306            self._values[attr] = value
1307            # implicitly parent unparented objects assigned as params
1308            if isSimObjectOrVector(value) and not value.has_parent():
1309                self.add_child(attr, value)
1310            # set the human-readable value dict if this is a param
1311            # with a literal value and is not being set as an object
1312            # or proxy.
1313            if not (isSimObjectOrVector(value) or\
1314                    isinstance(value, m5.proxy.BaseProxy)):
1315                self._hr_values[attr] = hr_value
1316
1317            return
1318
1319        # if RHS is a SimObject, it's an implicit child assignment
1320        if isSimObjectOrSequence(value):
1321            self.add_child(attr, value)
1322            return
1323
1324        # no valid assignment... raise exception
1325        raise AttributeError("Class %s has no parameter %s" \
1326              % (self.__class__.__name__, attr))
1327
1328
1329    # this hack allows tacking a '[0]' onto parameters that may or may
1330    # not be vectors, and always getting the first element (e.g. cpus)
1331    def __getitem__(self, key):
1332        if key == 0:
1333            return self
1334        raise IndexError("Non-zero index '%s' to SimObject" % key)
1335
1336    # this hack allows us to iterate over a SimObject that may
1337    # not be a vector, so we can call a loop over it and get just one
1338    # element.
1339    def __len__(self):
1340        return 1
1341
1342    # Also implemented by SimObjectVector
1343    def clear_parent(self, old_parent):
1344        assert self._parent is old_parent
1345        self._parent = None
1346
1347    # Also implemented by SimObjectVector
1348    def set_parent(self, parent, name):
1349        self._parent = parent
1350        self._name = name
1351
1352    # Return parent object of this SimObject, not implemented by
1353    # SimObjectVector because the elements in a SimObjectVector may not share
1354    # the same parent
1355    def get_parent(self):
1356        return self._parent
1357
1358    # Also implemented by SimObjectVector
1359    def get_name(self):
1360        return self._name
1361
1362    # Also implemented by SimObjectVector
1363    def has_parent(self):
1364        return self._parent is not None
1365
1366    # clear out child with given name. This code is not likely to be exercised.
1367    # See comment in add_child.
1368    def clear_child(self, name):
1369        child = self._children[name]
1370        child.clear_parent(self)
1371        del self._children[name]
1372
1373    # Add a new child to this object.
1374    def add_child(self, name, child):
1375        child = coerceSimObjectOrVector(child)
1376        if child.has_parent():
1377            warn("add_child('%s'): child '%s' already has parent", name,
1378                child.get_name())
1379        if name in self._children:
1380            # This code path had an undiscovered bug that would make it fail
1381            # at runtime. It had been here for a long time and was only
1382            # exposed by a buggy script. Changes here will probably not be
1383            # exercised without specialized testing.
1384            self.clear_child(name)
1385        child.set_parent(self, name)
1386        if not isNullPointer(child):
1387            self._children[name] = child
1388
1389    # Take SimObject-valued parameters that haven't been explicitly
1390    # assigned as children and make them children of the object that
1391    # they were assigned to as a parameter value.  This guarantees
1392    # that when we instantiate all the parameter objects we're still
1393    # inside the configuration hierarchy.
1394    def adoptOrphanParams(self):
1395        for key,val in self._values.items():
1396            if not isSimObjectVector(val) and isSimObjectSequence(val):
1397                # need to convert raw SimObject sequences to
1398                # SimObjectVector class so we can call has_parent()
1399                val = SimObjectVector(val)
1400                self._values[key] = val
1401            if isSimObjectOrVector(val) and not val.has_parent():
1402                warn("%s adopting orphan SimObject param '%s'", self, key)
1403                self.add_child(key, val)
1404
1405    def path(self):
1406        if not self._parent:
1407            return '<orphan %s>' % self.__class__
1408        elif isinstance(self._parent, MetaSimObject):
1409            return str(self.__class__)
1410
1411        ppath = self._parent.path()
1412        if ppath == 'root':
1413            return self._name
1414        return ppath + "." + self._name
1415
1416    def path_list(self):
1417        if self._parent:
1418            return self._parent.path_list() + [ self._name, ]
1419        else:
1420            # Don't include the root node
1421            return []
1422
1423    def __str__(self):
1424        return self.path()
1425
1426    def config_value(self):
1427        return self.path()
1428
1429    def ini_str(self):
1430        return self.path()
1431
1432    def find_any(self, ptype):
1433        if isinstance(self, ptype):
1434            return self, True
1435
1436        found_obj = None
1437        for child in self._children.values():
1438            visited = False
1439            if hasattr(child, '_visited'):
1440              visited = getattr(child, '_visited')
1441
1442            if isinstance(child, ptype) and not visited:
1443                if found_obj != None and child != found_obj:
1444                    raise AttributeError(
1445                          'parent.any matched more than one: %s %s' % \
1446                          (found_obj.path, child.path))
1447                found_obj = child
1448        # search param space
1449        for pname,pdesc in self._params.items():
1450            if issubclass(pdesc.ptype, ptype):
1451                match_obj = self._values[pname]
1452                if found_obj != None and found_obj != match_obj:
1453                    raise AttributeError(
1454                          'parent.any matched more than one: %s and %s' % \
1455                          (found_obj.path, match_obj.path))
1456                found_obj = match_obj
1457        return found_obj, found_obj != None
1458
1459    def find_all(self, ptype):
1460        all = {}
1461        # search children
1462        for child in self._children.values():
1463            # a child could be a list, so ensure we visit each item
1464            if isinstance(child, list):
1465                children = child
1466            else:
1467                children = [child]
1468
1469            for child in children:
1470                if isinstance(child, ptype) and not isproxy(child) and \
1471                        not isNullPointer(child):
1472                    all[child] = True
1473                if isSimObject(child):
1474                    # also add results from the child itself
1475                    child_all, done = child.find_all(ptype)
1476                    all.update(dict(zip(child_all, [done] * len(child_all))))
1477        # search param space
1478        for pname,pdesc in self._params.items():
1479            if issubclass(pdesc.ptype, ptype):
1480                match_obj = self._values[pname]
1481                if not isproxy(match_obj) and not isNullPointer(match_obj):
1482                    all[match_obj] = True
1483        # Also make sure to sort the keys based on the objects' path to
1484        # ensure that the order is the same on all hosts
1485        return sorted(all.keys(), key = lambda o: o.path()), True
1486
1487    def unproxy(self, base):
1488        return self
1489
1490    def unproxyParams(self):
1491        for param in self._params.keys():
1492            value = self._values.get(param)
1493            if value != None and isproxy(value):
1494                try:
1495                    value = value.unproxy(self)
1496                except:
1497                    print("Error in unproxying param '%s' of %s" %
1498                          (param, self.path()))
1499                    raise
1500                setattr(self, param, value)
1501
1502        # Unproxy ports in sorted order so that 'append' operations on
1503        # vector ports are done in a deterministic fashion.
1504        port_names = list(self._ports.keys())
1505        port_names.sort()
1506        for port_name in port_names:
1507            port = self._port_refs.get(port_name)
1508            if port != None:
1509                port.unproxy(self)
1510
1511    def print_ini(self, ini_file):
1512        print('[' + self.path() + ']', file=ini_file)    # .ini section header
1513
1514        instanceDict[self.path()] = self
1515
1516        if hasattr(self, 'type'):
1517            print('type=%s' % self.type, file=ini_file)
1518
1519        if len(self._children.keys()):
1520            print('children=%s' %
1521                  ' '.join(self._children[n].get_name()
1522                           for n in sorted(self._children.keys())),
1523                  file=ini_file)
1524
1525        for param in sorted(self._params.keys()):
1526            value = self._values.get(param)
1527            if value != None:
1528                print('%s=%s' % (param, self._values[param].ini_str()),
1529                      file=ini_file)
1530
1531        for port_name in sorted(self._ports.keys()):
1532            port = self._port_refs.get(port_name, None)
1533            if port != None:
1534                print('%s=%s' % (port_name, port.ini_str()), file=ini_file)
1535
1536        print(file=ini_file)        # blank line between objects
1537
1538    # generate a tree of dictionaries expressing all the parameters in the
1539    # instantiated system for use by scripts that want to do power, thermal
1540    # visualization, and other similar tasks
1541    def get_config_as_dict(self):
1542        d = attrdict()
1543        if hasattr(self, 'type'):
1544            d.type = self.type
1545        if hasattr(self, 'cxx_class'):
1546            d.cxx_class = self.cxx_class
1547        # Add the name and path of this object to be able to link to
1548        # the stats
1549        d.name = self.get_name()
1550        d.path = self.path()
1551
1552        for param in sorted(self._params.keys()):
1553            value = self._values.get(param)
1554            if value != None:
1555                d[param] = value.config_value()
1556
1557        for n in sorted(self._children.keys()):
1558            child = self._children[n]
1559            # Use the name of the attribute (and not get_name()) as
1560            # the key in the JSON dictionary to capture the hierarchy
1561            # in the Python code that assembled this system
1562            d[n] = child.get_config_as_dict()
1563
1564        for port_name in sorted(self._ports.keys()):
1565            port = self._port_refs.get(port_name, None)
1566            if port != None:
1567                # Represent each port with a dictionary containing the
1568                # prominent attributes
1569                d[port_name] = port.get_config_as_dict()
1570
1571        return d
1572
1573    def getCCParams(self):
1574        if self._ccParams:
1575            return self._ccParams
1576
1577        cc_params_struct = getattr(m5.internal.params, '%sParams' % self.type)
1578        cc_params = cc_params_struct()
1579        cc_params.name = str(self)
1580
1581        param_names = list(self._params.keys())
1582        param_names.sort()
1583        for param in param_names:
1584            value = self._values.get(param)
1585            if value is None:
1586                fatal("%s.%s without default or user set value",
1587                      self.path(), param)
1588
1589            value = value.getValue()
1590            if isinstance(self._params[param], VectorParamDesc):
1591                assert isinstance(value, list)
1592                vec = getattr(cc_params, param)
1593                assert not len(vec)
1594                # Some types are exposed as opaque types. They support
1595                # the append operation unlike the automatically
1596                # wrapped types.
1597                if isinstance(vec, list):
1598                    setattr(cc_params, param, list(value))
1599                else:
1600                    for v in value:
1601                        getattr(cc_params, param).append(v)
1602            else:
1603                setattr(cc_params, param, value)
1604
1605        port_names = list(self._ports.keys())
1606        port_names.sort()
1607        for port_name in port_names:
1608            port = self._port_refs.get(port_name, None)
1609            if port != None:
1610                port_count = len(port)
1611            else:
1612                port_count = 0
1613            setattr(cc_params, 'port_' + port_name + '_connection_count',
1614                    port_count)
1615        self._ccParams = cc_params
1616        return self._ccParams
1617
1618    # Get C++ object corresponding to this object, calling C++ if
1619    # necessary to construct it.  Does *not* recursively create
1620    # children.
1621    def getCCObject(self):
1622        if not self._ccObject:
1623            # Make sure this object is in the configuration hierarchy
1624            if not self._parent and not isRoot(self):
1625                raise RuntimeError("Attempt to instantiate orphan node")
1626            # Cycles in the configuration hierarchy are not supported. This
1627            # will catch the resulting recursion and stop.
1628            self._ccObject = -1
1629            if not self.abstract:
1630                params = self.getCCParams()
1631                self._ccObject = params.create()
1632        elif self._ccObject == -1:
1633            raise RuntimeError("%s: Cycle found in configuration hierarchy." \
1634                  % self.path())
1635        return self._ccObject
1636
1637    def descendants(self):
1638        yield self
1639        # The order of the dict is implementation dependent, so sort
1640        # it based on the key (name) to ensure the order is the same
1641        # on all hosts
1642        for (name, child) in sorted(self._children.items()):
1643            for obj in child.descendants():
1644                yield obj
1645
1646    # Call C++ to create C++ object corresponding to this object
1647    def createCCObject(self):
1648        self.getCCParams()
1649        self.getCCObject() # force creation
1650
1651    def getValue(self):
1652        return self.getCCObject()
1653
1654    @cxxMethod(return_value_policy="reference")
1655    def getPort(self, if_name, idx):
1656        pass
1657
1658    # Create C++ port connections corresponding to the connections in
1659    # _port_refs
1660    def connectPorts(self):
1661        # Sort the ports based on their attribute name to ensure the
1662        # order is the same on all hosts
1663        for (attr, portRef) in sorted(self._port_refs.items()):
1664            portRef.ccConnect()
1665
1666    # Default function for generating the device structure.
1667    # Can be overloaded by the inheriting class
1668    def generateDeviceTree(self, state):
1669        return # return without yielding anything
1670        yield  # make this function a (null) generator
1671
1672    def recurseDeviceTree(self, state):
1673        for child in self._children.values():
1674            for item in child: # For looping over SimObjectVectors
1675                for dt in item.generateDeviceTree(state):
1676                    yield dt
1677
1678    # On a separate method otherwise certain buggy Python versions
1679    # would fail with: SyntaxError: unqualified exec is not allowed
1680    # in function 'apply_config'
1681    def _apply_config_get_dict(self):
1682        return {
1683            child_name: SimObjectCliWrapper(
1684                iter(self._children[child_name]))
1685            for child_name in self._children
1686        }
1687
1688    def apply_config(self, params):
1689        """
1690        exec a list of Python code strings contained in params.
1691
1692        The only exposed globals to those strings are the child
1693        SimObjects of this node.
1694
1695        This function is intended to allow users to modify SimObject
1696        parameters from the command line with Python statements.
1697        """
1698        d = self._apply_config_get_dict()
1699        for param in params:
1700            exec(param, d)
1701
1702# Function to provide to C++ so it can look up instances based on paths
1703def resolveSimObject(name):
1704    obj = instanceDict[name]
1705    return obj.getCCObject()
1706
1707def isSimObject(value):
1708    return isinstance(value, SimObject)
1709
1710def isSimObjectClass(value):
1711    return issubclass(value, SimObject)
1712
1713def isSimObjectVector(value):
1714    return isinstance(value, SimObjectVector)
1715
1716def isSimObjectSequence(value):
1717    if not isinstance(value, (list, tuple)) or len(value) == 0:
1718        return False
1719
1720    for val in value:
1721        if not isNullPointer(val) and not isSimObject(val):
1722            return False
1723
1724    return True
1725
1726def isSimObjectOrSequence(value):
1727    return isSimObject(value) or isSimObjectSequence(value)
1728
1729def isRoot(obj):
1730    from m5.objects import Root
1731    return obj and obj is Root.getInstance()
1732
1733def isSimObjectOrVector(value):
1734    return isSimObject(value) or isSimObjectVector(value)
1735
1736def tryAsSimObjectOrVector(value):
1737    if isSimObjectOrVector(value):
1738        return value
1739    if isSimObjectSequence(value):
1740        return SimObjectVector(value)
1741    return None
1742
1743def coerceSimObjectOrVector(value):
1744    value = tryAsSimObjectOrVector(value)
1745    if value is None:
1746        raise TypeError("SimObject or SimObjectVector expected")
1747    return value
1748
1749baseClasses = allClasses.copy()
1750baseInstances = instanceDict.copy()
1751
1752def clear():
1753    global allClasses, instanceDict, noCxxHeader
1754
1755    allClasses = baseClasses.copy()
1756    instanceDict = baseInstances.copy()
1757    noCxxHeader = False
1758
1759# __all__ defines the list of symbols that get exported when
1760# 'from config import *' is invoked.  Try to keep this reasonably
1761# short to avoid polluting other namespaces.
1762__all__ = [
1763    'SimObject',
1764    'cxxMethod',
1765    'PyBindMethod',
1766    'PyBindProperty',
1767]
1768