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