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