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