SimObject.py revision 3101
1# Copyright (c) 2004-2006 The Regents of The University of Michigan
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27# Authors: Steve Reinhardt
28#          Nathan Binkert
29
30import sys, types
31
32import m5
33from m5 import panic, cc_main
34from convert import *
35from multidict import multidict
36
37noDot = False
38try:
39    import pydot
40except:
41    noDot = True
42
43#####################################################################
44#
45# M5 Python Configuration Utility
46#
47# The basic idea is to write simple Python programs that build Python
48# objects corresponding to M5 SimObjects for the desired simulation
49# configuration.  For now, the Python emits a .ini file that can be
50# parsed by M5.  In the future, some tighter integration between M5
51# and the Python interpreter may allow bypassing the .ini file.
52#
53# Each SimObject class in M5 is represented by a Python class with the
54# same name.  The Python inheritance tree mirrors the M5 C++ tree
55# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
56# SimObjects inherit from a single SimObject base class).  To specify
57# an instance of an M5 SimObject in a configuration, the user simply
58# instantiates the corresponding Python object.  The parameters for
59# that SimObject are given by assigning to attributes of the Python
60# object, either using keyword assignment in the constructor or in
61# separate assignment statements.  For example:
62#
63# cache = BaseCache(size='64KB')
64# cache.hit_latency = 3
65# cache.assoc = 8
66#
67# The magic lies in the mapping of the Python attributes for SimObject
68# classes to the actual SimObject parameter specifications.  This
69# allows parameter validity checking in the Python code.  Continuing
70# the example above, the statements "cache.blurfl=3" or
71# "cache.assoc='hello'" would both result in runtime errors in Python,
72# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
73# parameter requires an integer, respectively.  This magic is done
74# primarily by overriding the special __setattr__ method that controls
75# assignment to object attributes.
76#
77# Once a set of Python objects have been instantiated in a hierarchy,
78# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
79# will generate a .ini file.
80#
81#####################################################################
82
83# dict to look up SimObjects based on path
84instanceDict = {}
85
86# The metaclass for SimObject.  This class controls how new classes
87# that derive from SimObject are instantiated, and provides inherited
88# class behavior (just like a class controls how instances of that
89# class are instantiated, and provides inherited instance behavior).
90class MetaSimObject(type):
91    # Attributes that can be set only at initialization time
92    init_keywords = { 'abstract' : types.BooleanType,
93                      'type' : types.StringType }
94    # Attributes that can be set any time
95    keywords = { 'check' : types.FunctionType,
96                 'cxx_type' : types.StringType,
97                 'cxx_predecls' : types.ListType,
98                 'swig_predecls' : types.ListType }
99
100    # __new__ is called before __init__, and is where the statements
101    # in the body of the class definition get loaded into the class's
102    # __dict__.  We intercept this to filter out parameter & port assignments
103    # and only allow "private" attributes to be passed to the base
104    # __new__ (starting with underscore).
105    def __new__(mcls, name, bases, dict):
106        # Copy "private" attributes, functions, and classes to the
107        # official dict.  Everything else goes in _init_dict to be
108        # filtered in __init__.
109        cls_dict = {}
110        value_dict = {}
111        for key,val in dict.items():
112            if key.startswith('_') or isinstance(val, (types.FunctionType,
113                                                       types.TypeType)):
114                cls_dict[key] = val
115            else:
116                # must be a param/port setting
117                value_dict[key] = val
118        cls_dict['_value_dict'] = value_dict
119        return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
120
121    # subclass initialization
122    def __init__(cls, name, bases, dict):
123        # calls type.__init__()... I think that's a no-op, but leave
124        # it here just in case it's not.
125        super(MetaSimObject, cls).__init__(name, bases, dict)
126
127        # initialize required attributes
128
129        # class-only attributes
130        cls._params = multidict() # param descriptions
131        cls._ports = multidict()  # port descriptions
132
133        # class or instance attributes
134        cls._values = multidict()   # param values
135        cls._port_map = multidict() # port bindings
136        cls._instantiated = False # really instantiated, cloned, or subclassed
137
138        # We don't support multiple inheritance.  If you want to, you
139        # must fix multidict to deal with it properly.
140        if len(bases) > 1:
141            raise TypeError, "SimObjects do not support multiple inheritance"
142
143        base = bases[0]
144
145        # Set up general inheritance via multidicts.  A subclass will
146        # inherit all its settings from the base class.  The only time
147        # the following is not true is when we define the SimObject
148        # class itself (in which case the multidicts have no parent).
149        if isinstance(base, MetaSimObject):
150            cls._params.parent = base._params
151            cls._ports.parent = base._ports
152            cls._values.parent = base._values
153            cls._port_map.parent = base._port_map
154            # mark base as having been subclassed
155            base._instantiated = True
156
157        # Now process the _value_dict items.  They could be defining
158        # new (or overriding existing) parameters or ports, setting
159        # class keywords (e.g., 'abstract'), or setting parameter
160        # values or port bindings.  The first 3 can only be set when
161        # the class is defined, so we handle them here.  The others
162        # can be set later too, so just emulate that by calling
163        # setattr().
164        for key,val in cls._value_dict.items():
165            # param descriptions
166            if isinstance(val, ParamDesc):
167                cls._new_param(key, val)
168
169            # port objects
170            elif isinstance(val, Port):
171                cls._ports[key] = val
172
173            # init-time-only keywords
174            elif cls.init_keywords.has_key(key):
175                cls._set_keyword(key, val, cls.init_keywords[key])
176
177            # default: use normal path (ends up in __setattr__)
178            else:
179                setattr(cls, key, val)
180
181        cls.cxx_type = cls.type + '*'
182        # A forward class declaration is sufficient since we are just
183        # declaring a pointer.
184        cls.cxx_predecls = ['class %s;' % cls.type]
185        cls.swig_predecls = cls.cxx_predecls
186
187    def _set_keyword(cls, keyword, val, kwtype):
188        if not isinstance(val, kwtype):
189            raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
190                  (keyword, type(val), kwtype)
191        if isinstance(val, types.FunctionType):
192            val = classmethod(val)
193        type.__setattr__(cls, keyword, val)
194
195    def _new_param(cls, name, pdesc):
196        # each param desc should be uniquely assigned to one variable
197        assert(not hasattr(pdesc, 'name'))
198        pdesc.name = name
199        cls._params[name] = pdesc
200        if hasattr(pdesc, 'default'):
201            setattr(cls, name, pdesc.default)
202
203    # Set attribute (called on foo.attr = value when foo is an
204    # instance of class cls).
205    def __setattr__(cls, attr, value):
206        # normal processing for private attributes
207        if attr.startswith('_'):
208            type.__setattr__(cls, attr, value)
209            return
210
211        if cls.keywords.has_key(attr):
212            cls._set_keyword(attr, value, cls.keywords[attr])
213            return
214
215        if cls._ports.has_key(attr):
216            self._ports[attr].connect(self, attr, value)
217            return
218
219        if isSimObjectOrSequence(value) and cls._instantiated:
220            raise RuntimeError, \
221                  "cannot set SimObject parameter '%s' after\n" \
222                  "    class %s has been instantiated or subclassed" \
223                  % (attr, cls.__name__)
224
225        # check for param
226        param = cls._params.get(attr, None)
227        if param:
228            try:
229                cls._values[attr] = param.convert(value)
230            except Exception, e:
231                msg = "%s\nError setting param %s.%s to %s\n" % \
232                      (e, cls.__name__, attr, value)
233                e.args = (msg, )
234                raise
235        elif isSimObjectOrSequence(value):
236            # if RHS is a SimObject, it's an implicit child assignment
237            cls._values[attr] = value
238        else:
239            raise AttributeError, \
240                  "Class %s has no parameter \'%s\'" % (cls.__name__, attr)
241
242    def __getattr__(cls, attr):
243        if cls._values.has_key(attr):
244            return cls._values[attr]
245
246        raise AttributeError, \
247              "object '%s' has no attribute '%s'" % (cls.__name__, attr)
248
249    def __str__(cls):
250        return cls.__name__
251
252    def cxx_decl(cls):
253        code = "#ifndef __PARAMS__%s\n#define __PARAMS__%s\n\n" % (cls, cls)
254
255        if str(cls) != 'SimObject':
256            base = cls.__bases__[0].type
257        else:
258            base = None
259
260        # The 'dict' attribute restricts us to the params declared in
261        # the object itself, not including inherited params (which
262        # will also be inherited from the base class's param struct
263        # here).
264        params = cls._params.dict.values()
265        try:
266            ptypes = [p.ptype for p in params]
267        except:
268            print cls, p, p.ptype_str
269            print params
270            raise
271
272        # get a list of lists of predeclaration lines
273        predecls = [p.cxx_predecls() for p in params]
274        # flatten
275        predecls = reduce(lambda x,y:x+y, predecls, [])
276        # remove redundant lines
277        predecls2 = []
278        for pd in predecls:
279            if pd not in predecls2:
280                predecls2.append(pd)
281        predecls2.sort()
282        code += "\n".join(predecls2)
283        code += "\n\n";
284
285        if base:
286            code += '#include "params/%s.hh"\n\n' % base
287
288        # Generate declarations for locally defined enumerations.
289        enum_ptypes = [t for t in ptypes if issubclass(t, Enum)]
290        if enum_ptypes:
291            code += "\n".join([t.cxx_decl() for t in enum_ptypes])
292            code += "\n\n"
293
294        # now generate the actual param struct
295        code += "struct %sParams" % cls
296        if base:
297            code += " : public %sParams" % base
298        code += " {\n"
299        decls = [p.cxx_decl() for p in params]
300        decls.sort()
301        code += "".join(["    %s\n" % d for d in decls])
302        code += "};\n"
303
304        # close #ifndef __PARAMS__* guard
305        code += "\n#endif\n"
306        return code
307
308    def swig_decl(cls):
309
310        code = '%%module %sParams\n' % cls
311
312        if str(cls) != 'SimObject':
313            base = cls.__bases__[0].type
314        else:
315            base = None
316
317        # The 'dict' attribute restricts us to the params declared in
318        # the object itself, not including inherited params (which
319        # will also be inherited from the base class's param struct
320        # here).
321        params = cls._params.dict.values()
322        ptypes = [p.ptype for p in params]
323
324        # get a list of lists of predeclaration lines
325        predecls = [p.swig_predecls() for p in params]
326        # flatten
327        predecls = reduce(lambda x,y:x+y, predecls, [])
328        # remove redundant lines
329        predecls2 = []
330        for pd in predecls:
331            if pd not in predecls2:
332                predecls2.append(pd)
333        predecls2.sort()
334        code += "\n".join(predecls2)
335        code += "\n\n";
336
337        if base:
338            code += '%%import "python/m5/swig/%sParams.i"\n\n' % base
339
340        code += '%{\n'
341        code += '#include "params/%s.hh"\n' % cls
342        code += '%}\n\n'
343        code += '%%include "params/%s.hh"\n\n' % cls
344
345        return code
346
347# The SimObject class is the root of the special hierarchy.  Most of
348# the code in this class deals with the configuration hierarchy itself
349# (parent/child node relationships).
350class SimObject(object):
351    # Specify metaclass.  Any class inheriting from SimObject will
352    # get this metaclass.
353    __metaclass__ = MetaSimObject
354    type = 'SimObject'
355
356    name = Param.String("Object name")
357
358    # Initialize new instance.  For objects with SimObject-valued
359    # children, we need to recursively clone the classes represented
360    # by those param values as well in a consistent "deep copy"-style
361    # fashion.  That is, we want to make sure that each instance is
362    # cloned only once, and that if there are multiple references to
363    # the same original object, we end up with the corresponding
364    # cloned references all pointing to the same cloned instance.
365    def __init__(self, **kwargs):
366        ancestor = kwargs.get('_ancestor')
367        memo_dict = kwargs.get('_memo')
368        if memo_dict is None:
369            # prepare to memoize any recursively instantiated objects
370            memo_dict = {}
371        elif ancestor:
372            # memoize me now to avoid problems with recursive calls
373            memo_dict[ancestor] = self
374
375        if not ancestor:
376            ancestor = self.__class__
377        ancestor._instantiated = True
378
379        # initialize required attributes
380        self._parent = None
381        self._children = {}
382        self._ccObject = None  # pointer to C++ object
383        self._instantiated = False # really "cloned"
384
385        # Inherit parameter values from class using multidict so
386        # individual value settings can be overridden.
387        self._values = multidict(ancestor._values)
388        # clone SimObject-valued parameters
389        for key,val in ancestor._values.iteritems():
390            if isSimObject(val):
391                setattr(self, key, val(_memo=memo_dict))
392            elif isSimObjectSequence(val) and len(val):
393                setattr(self, key, [ v(_memo=memo_dict) for v in val ])
394        # clone port references.  no need to use a multidict here
395        # since we will be creating new references for all ports.
396        self._port_map = {}
397        for key,val in ancestor._port_map.iteritems():
398            self._port_map[key] = applyOrMap(val, 'clone', memo_dict)
399        # apply attribute assignments from keyword args, if any
400        for key,val in kwargs.iteritems():
401            setattr(self, key, val)
402
403    # "Clone" the current instance by creating another instance of
404    # this instance's class, but that inherits its parameter values
405    # and port mappings from the current instance.  If we're in a
406    # "deep copy" recursive clone, check the _memo dict to see if
407    # we've already cloned this instance.
408    def __call__(self, **kwargs):
409        memo_dict = kwargs.get('_memo')
410        if memo_dict is None:
411            # no memo_dict: must be top-level clone operation.
412            # this is only allowed at the root of a hierarchy
413            if self._parent:
414                raise RuntimeError, "attempt to clone object %s " \
415                      "not at the root of a tree (parent = %s)" \
416                      % (self, self._parent)
417            # create a new dict and use that.
418            memo_dict = {}
419            kwargs['_memo'] = memo_dict
420        elif memo_dict.has_key(self):
421            # clone already done & memoized
422            return memo_dict[self]
423        return self.__class__(_ancestor = self, **kwargs)
424
425    def __getattr__(self, attr):
426        if self._ports.has_key(attr):
427            # return reference that can be assigned to another port
428            # via __setattr__
429            return self._ports[attr].makeRef(self, attr)
430
431        if self._values.has_key(attr):
432            return self._values[attr]
433
434        raise AttributeError, "object '%s' has no attribute '%s'" \
435              % (self.__class__.__name__, attr)
436
437    # Set attribute (called on foo.attr = value when foo is an
438    # instance of class cls).
439    def __setattr__(self, attr, value):
440        # normal processing for private attributes
441        if attr.startswith('_'):
442            object.__setattr__(self, attr, value)
443            return
444
445        if self._ports.has_key(attr):
446            # set up port connection
447            self._ports[attr].connect(self, attr, value)
448            return
449
450        if isSimObjectOrSequence(value) and self._instantiated:
451            raise RuntimeError, \
452                  "cannot set SimObject parameter '%s' after\n" \
453                  "    instance been cloned %s" % (attr, `self`)
454
455        # must be SimObject param
456        param = self._params.get(attr, None)
457        if param:
458            try:
459                value = param.convert(value)
460            except Exception, e:
461                msg = "%s\nError setting param %s.%s to %s\n" % \
462                      (e, self.__class__.__name__, attr, value)
463                e.args = (msg, )
464                raise
465        elif isSimObjectOrSequence(value):
466            pass
467        else:
468            raise AttributeError, "Class %s has no parameter %s" \
469                  % (self.__class__.__name__, attr)
470
471        # clear out old child with this name, if any
472        self.clear_child(attr)
473
474        if isSimObject(value):
475            value.set_path(self, attr)
476        elif isSimObjectSequence(value):
477            value = SimObjVector(value)
478            [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)]
479
480        self._values[attr] = value
481
482    # this hack allows tacking a '[0]' onto parameters that may or may
483    # not be vectors, and always getting the first element (e.g. cpus)
484    def __getitem__(self, key):
485        if key == 0:
486            return self
487        raise TypeError, "Non-zero index '%s' to SimObject" % key
488
489    # clear out children with given name, even if it's a vector
490    def clear_child(self, name):
491        if not self._children.has_key(name):
492            return
493        child = self._children[name]
494        if isinstance(child, SimObjVector):
495            for i in xrange(len(child)):
496                del self._children["s%d" % (name, i)]
497        del self._children[name]
498
499    def add_child(self, name, value):
500        self._children[name] = value
501
502    def set_path(self, parent, name):
503        if not self._parent:
504            self._parent = parent
505            self._name = name
506            parent.add_child(name, self)
507
508    def path(self):
509        if not self._parent:
510            return 'root'
511        ppath = self._parent.path()
512        if ppath == 'root':
513            return self._name
514        return ppath + "." + self._name
515
516    def __str__(self):
517        return self.path()
518
519    def ini_str(self):
520        return self.path()
521
522    def find_any(self, ptype):
523        if isinstance(self, ptype):
524            return self, True
525
526        found_obj = None
527        for child in self._children.itervalues():
528            if isinstance(child, ptype):
529                if found_obj != None and child != found_obj:
530                    raise AttributeError, \
531                          'parent.any matched more than one: %s %s' % \
532                          (found_obj.path, child.path)
533                found_obj = child
534        # search param space
535        for pname,pdesc in self._params.iteritems():
536            if issubclass(pdesc.ptype, ptype):
537                match_obj = self._values[pname]
538                if found_obj != None and found_obj != match_obj:
539                    raise AttributeError, \
540                          'parent.any matched more than one: %s' % obj.path
541                found_obj = match_obj
542        return found_obj, found_obj != None
543
544    def unproxy(self, base):
545        return self
546
547    def print_ini(self):
548        print '[' + self.path() + ']'	# .ini section header
549
550        instanceDict[self.path()] = self
551
552        if hasattr(self, 'type') and not isinstance(self, ParamContext):
553            print 'type=%s' % self.type
554
555        child_names = self._children.keys()
556        child_names.sort()
557        np_child_names = [c for c in child_names \
558                          if not isinstance(self._children[c], ParamContext)]
559        if len(np_child_names):
560            print 'children=%s' % ' '.join(np_child_names)
561
562        param_names = self._params.keys()
563        param_names.sort()
564        for param in param_names:
565            value = self._values.get(param, None)
566            if value != None:
567                if isproxy(value):
568                    try:
569                        value = value.unproxy(self)
570                    except:
571                        print >> sys.stderr, \
572                              "Error in unproxying param '%s' of %s" % \
573                              (param, self.path())
574                        raise
575                    setattr(self, param, value)
576                print '%s=%s' % (param, self._values[param].ini_str())
577
578        print	# blank line between objects
579
580        for child in child_names:
581            self._children[child].print_ini()
582
583    # Call C++ to create C++ object corresponding to this object and
584    # (recursively) all its children
585    def createCCObject(self):
586        self.getCCObject() # force creation
587        for child in self._children.itervalues():
588            child.createCCObject()
589
590    # Get C++ object corresponding to this object, calling C++ if
591    # necessary to construct it.  Does *not* recursively create
592    # children.
593    def getCCObject(self):
594        if not self._ccObject:
595            self._ccObject = -1 # flag to catch cycles in recursion
596            self._ccObject = cc_main.createSimObject(self.path())
597        elif self._ccObject == -1:
598            raise RuntimeError, "%s: recursive call to getCCObject()" \
599                  % self.path()
600        return self._ccObject
601
602    # Create C++ port connections corresponding to the connections in
603    # _port_map (& recursively for all children)
604    def connectPorts(self):
605        for portRef in self._port_map.itervalues():
606            applyOrMap(portRef, 'ccConnect')
607        for child in self._children.itervalues():
608            child.connectPorts()
609
610    def startDrain(self, drain_event, recursive):
611        count = 0
612        # ParamContexts don't serialize
613        if isinstance(self, SimObject) and not isinstance(self, ParamContext):
614            count += self._ccObject.drain(drain_event)
615        if recursive:
616            for child in self._children.itervalues():
617                count += child.startDrain(drain_event, True)
618        return count
619
620    def resume(self):
621        if isinstance(self, SimObject) and not isinstance(self, ParamContext):
622            self._ccObject.resume()
623        for child in self._children.itervalues():
624            child.resume()
625
626    def changeTiming(self, mode):
627        if isinstance(self, System):
628            self._ccObject.setMemoryMode(mode)
629        for child in self._children.itervalues():
630            child.changeTiming(mode)
631
632    def takeOverFrom(self, old_cpu):
633        cpu_ptr = cc_main.convertToBaseCPUPtr(old_cpu._ccObject)
634        self._ccObject.takeOverFrom(cpu_ptr)
635
636    # generate output file for 'dot' to display as a pretty graph.
637    # this code is currently broken.
638    def outputDot(self, dot):
639        label = "{%s|" % self.path
640        if isSimObject(self.realtype):
641            label +=  '%s|' % self.type
642
643        if self.children:
644            # instantiate children in same order they were added for
645            # backward compatibility (else we can end up with cpu1
646            # before cpu0).
647            for c in self.children:
648                dot.add_edge(pydot.Edge(self.path,c.path, style="bold"))
649
650        simobjs = []
651        for param in self.params:
652            try:
653                if param.value is None:
654                    raise AttributeError, 'Parameter with no value'
655
656                value = param.value
657                string = param.string(value)
658            except Exception, e:
659                msg = 'exception in %s:%s\n%s' % (self.name, param.name, e)
660                e.args = (msg, )
661                raise
662
663            if isSimObject(param.ptype) and string != "Null":
664                simobjs.append(string)
665            else:
666                label += '%s = %s\\n' % (param.name, string)
667
668        for so in simobjs:
669            label += "|<%s> %s" % (so, so)
670            dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so,
671                                    tailport="w"))
672        label += '}'
673        dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label))
674
675        # recursively dump out children
676        for c in self.children:
677            c.outputDot(dot)
678
679class ParamContext(SimObject):
680    pass
681
682# Special class for NULL pointers.  Note the special check in
683# make_param_value() above that lets these be assigned where a
684# SimObject is required.
685# only one copy of a particular node
686class NullSimObject(object):
687    __metaclass__ = Singleton
688
689    def __call__(cls):
690        return cls
691
692    def _instantiate(self, parent = None, path = ''):
693        pass
694
695    def ini_str(self):
696        return 'Null'
697
698    def unproxy(self, base):
699        return self
700
701    def set_path(self, parent, name):
702        pass
703    def __str__(self):
704        return 'Null'
705
706# The only instance you'll ever need...
707Null = NULL = NullSimObject()
708
709def isSimObject(value):
710    return isinstance(value, SimObject)
711
712def isNullPointer(value):
713    return isinstance(value, NullSimObject)
714
715def isSimObjectSequence(value):
716    if not isinstance(value, (list, tuple)) or len(value) == 0:
717        return False
718
719    for val in value:
720        if not isNullPointer(val) and not isSimObject(val):
721            return False
722
723    return True
724
725def isSimObjectOrSequence(value):
726    return isSimObject(value) or isSimObjectSequence(value)
727
728# Function to provide to C++ so it can look up instances based on paths
729def resolveSimObject(name):
730    obj = instanceDict[name]
731    return obj.getCCObject()
732
733# __all__ defines the list of symbols that get exported when
734# 'from config import *' is invoked.  Try to keep this reasonably
735# short to avoid polluting other namespaces.
736__all__ = ['SimObject', 'ParamContext']
737
738