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