SimObject.py revision 1527
1# Copyright (c) 2004 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
27from __future__ import generators
28import os, re, sys, types, inspect
29
30from smartdict import SmartDict
31from convert import *
32
33noDot = False
34try:
35    import pydot
36except:
37    noDot = True
38
39env = SmartDict()
40env.update(os.environ)
41
42def panic(string):
43    print >>sys.stderr, 'panic:', string
44    sys.exit(1)
45
46def issequence(value):
47    return isinstance(value, tuple) or isinstance(value, list)
48
49class Singleton(type):
50    def __call__(cls, *args, **kwargs):
51        if hasattr(cls, '_instance'):
52            return cls._instance
53
54        cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
55        return cls._instance
56
57#####################################################################
58#
59# M5 Python Configuration Utility
60#
61# The basic idea is to write simple Python programs that build Python
62# objects corresponding to M5 SimObjects for the deisred simulation
63# configuration.  For now, the Python emits a .ini file that can be
64# parsed by M5.  In the future, some tighter integration between M5
65# and the Python interpreter may allow bypassing the .ini file.
66#
67# Each SimObject class in M5 is represented by a Python class with the
68# same name.  The Python inheritance tree mirrors the M5 C++ tree
69# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
70# SimObjects inherit from a single SimObject base class).  To specify
71# an instance of an M5 SimObject in a configuration, the user simply
72# instantiates the corresponding Python object.  The parameters for
73# that SimObject are given by assigning to attributes of the Python
74# object, either using keyword assignment in the constructor or in
75# separate assignment statements.  For example:
76#
77# cache = BaseCache('my_cache', root, size=64*K)
78# cache.hit_latency = 3
79# cache.assoc = 8
80#
81# (The first two constructor arguments specify the name of the created
82# cache and its parent node in the hierarchy.)
83#
84# The magic lies in the mapping of the Python attributes for SimObject
85# classes to the actual SimObject parameter specifications.  This
86# allows parameter validity checking in the Python code.  Continuing
87# the example above, the statements "cache.blurfl=3" or
88# "cache.assoc='hello'" would both result in runtime errors in Python,
89# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
90# parameter requires an integer, respectively.  This magic is done
91# primarily by overriding the special __setattr__ method that controls
92# assignment to object attributes.
93#
94# The Python module provides another class, ConfigNode, which is a
95# superclass of SimObject.  ConfigNode implements the parent/child
96# relationship for building the configuration hierarchy tree.
97# Concrete instances of ConfigNode can be used to group objects in the
98# hierarchy, but do not correspond to SimObjects themselves (like a
99# .ini section with "children=" but no "type=".
100#
101# Once a set of Python objects have been instantiated in a hierarchy,
102# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
103# will generate a .ini file.  See simple-4cpu.py for an example
104# (corresponding to m5-test/simple-4cpu.ini).
105#
106#####################################################################
107
108#####################################################################
109#
110# ConfigNode/SimObject classes
111#
112# The Python class hierarchy rooted by ConfigNode (which is the base
113# class of SimObject, which in turn is the base class of all other M5
114# SimObject classes) has special attribute behavior.  In general, an
115# object in this hierarchy has three categories of attribute-like
116# things:
117#
118# 1. Regular Python methods and variables.  These must start with an
119# underscore to be treated normally.
120#
121# 2. SimObject parameters.  These values are stored as normal Python
122# attributes, but all assignments to these attributes are checked
123# against the pre-defined set of parameters stored in the class's
124# _params dictionary.  Assignments to attributes that do not
125# correspond to predefined parameters, or that are not of the correct
126# type, incur runtime errors.
127#
128# 3. Hierarchy children.  The child nodes of a ConfigNode are stored
129# in the node's _children dictionary, but can be accessed using the
130# Python attribute dot-notation (just as they are printed out by the
131# simulator).  Children cannot be created using attribute assigment;
132# they must be added by specifying the parent node in the child's
133# constructor or using the '+=' operator.
134
135# The SimObject parameters are the most complex, for a few reasons.
136# First, both parameter descriptions and parameter values are
137# inherited.  Thus parameter description lookup must go up the
138# inheritance chain like normal attribute lookup, but this behavior
139# must be explicitly coded since the lookup occurs in each class's
140# _params attribute.  Second, because parameter values can be set
141# on SimObject classes (to implement default values), the parameter
142# checking behavior must be enforced on class attribute assignments as
143# well as instance attribute assignments.  Finally, because we allow
144# class specialization via inheritance (e.g., see the L1Cache class in
145# the simple-4cpu.py example), we must do parameter checking even on
146# class instantiation.  To provide all these features, we use a
147# metaclass to define most of the SimObject parameter behavior for
148# this class hierarchy.
149#
150#####################################################################
151
152class Proxy(object):
153    def __init__(self, path = ()):
154        self._object = None
155        self._path = path
156
157    def __getattr__(self, attr):
158        return Proxy(self._path + (attr, ))
159
160    def __setattr__(self, attr, value):
161        if not attr.startswith('_'):
162            raise AttributeError, 'cannot set attribute %s' % attr
163        super(Proxy, self).__setattr__(attr, value)
164
165    def _convert(self):
166        obj = self._object
167        for attr in self._path:
168            obj = obj.__getattribute__(attr)
169        return obj
170
171Super = Proxy()
172
173def isSubClass(value, cls):
174    try:
175        return issubclass(value, cls)
176    except:
177        return False
178
179def isConfigNode(value):
180    try:
181        return issubclass(value, ConfigNode)
182    except:
183        return False
184
185def isSimObject(value):
186    try:
187        return issubclass(value, SimObject)
188    except:
189        return False
190
191def isSimObjSequence(value):
192    if not issequence(value):
193        return False
194
195    for val in value:
196        if not isNullPointer(val) and not isConfigNode(val):
197            return False
198
199    return True
200
201def isParamContext(value):
202    try:
203        return issubclass(value, ParamContext)
204    except:
205        return False
206
207
208class_decorator = 'M5M5_SIMOBJECT_'
209expr_decorator = 'M5M5_EXPRESSION_'
210dot_decorator = '_M5M5_DOT_'
211
212param_types = {}
213
214class ParamType(object):
215    pass
216
217def add_param_types(ctx):
218    if isinstance(ctx, types.DictType):
219        source_dict = ctx
220    elif isinstance(ctx, types.ModuleType):
221        source_dict = ctx.__dict__
222    else:
223        raise TypeError, \
224              "m5.config.add_param_types requires dict or module as arg"
225    for key,val in source_dict.iteritems():
226        if isinstance(val, type) and issubclass(val, ParamType):
227            param_types[key] = val
228
229# The metaclass for ConfigNode (and thus for everything that derives
230# from ConfigNode, including SimObject).  This class controls how new
231# classes that derive from ConfigNode are instantiated, and provides
232# inherited class behavior (just like a class controls how instances
233# of that class are instantiated, and provides inherited instance
234# behavior).
235class MetaConfigNode(type):
236    # Attributes that can be set only at initialization time
237    init_keywords = {}
238    # Attributes that can be set any time
239    keywords = { 'check' : types.FunctionType,
240                 'children' : types.ListType }
241
242    # __new__ is called before __init__, and is where the statements
243    # in the body of the class definition get loaded into the class's
244    # __dict__.  We intercept this to filter out parameter assignments
245    # and only allow "private" attributes to be passed to the base
246    # __new__ (starting with underscore).
247    def __new__(mcls, name, bases, dict):
248        # Copy "private" attributes (including special methods such as __new__)
249        # to the official dict.  Everything else goes in _init_dict to be
250        # filtered in __init__.
251        cls_dict = {}
252        for key,val in dict.items():
253            if key.startswith('_'):
254                cls_dict[key] = val
255                del dict[key]
256        cls_dict['_init_dict'] = dict
257        return super(MetaConfigNode, mcls).__new__(mcls, name, bases, cls_dict)
258
259    # initialization
260    def __init__(cls, name, bases, dict):
261        super(MetaConfigNode, cls).__init__(name, bases, dict)
262
263        # initialize required attributes
264        cls._params = {}
265        cls._values = {}
266        cls._param_types = {}
267        cls._bases = [c for c in cls.__mro__ if isConfigNode(c)]
268        cls._anon_subclass_counter = 0
269
270        # If your parent has a value in it that's a config node, clone
271        # it.  Do this now so if we update any of the values'
272        # attributes we are updating the clone and not the original.
273        for base in cls._bases:
274            for key,val in base._values.iteritems():
275
276                # don't clone if (1) we're about to overwrite it with
277                # a local setting or (2) we've already cloned a copy
278                # from an earlier (more derived) base
279                if cls._init_dict.has_key(key) or cls._values.has_key(key):
280                    continue
281
282                if isConfigNode(val):
283                    cls._values[key] = val()
284                elif isSimObjSequence(val):
285                    cls._values[key] = [ v() for v in val ]
286                elif isNullPointer(val):
287                    cls._values[key] = val
288
289        # process param types from _init_dict, as these may be needed
290        # by param descriptions also in _init_dict
291        for key,val in cls._init_dict.items():
292            if isinstance(val, type) and issubclass(val, ParamType):
293                cls._param_types[key] = val
294                if not issubclass(val, ConfigNode):
295                    del cls._init_dict[key]
296
297        # now process remaining _init_dict items
298        for key,val in cls._init_dict.items():
299            # param descriptions
300            if isinstance(val, _Param):
301                cls._params[key] = val
302                # try to resolve local param types in local param_types scope
303                val.maybe_resolve_type(cls._param_types)
304
305            # init-time-only keywords
306            elif cls.init_keywords.has_key(key):
307                cls._set_keyword(key, val, cls.init_keywords[key])
308
309            # See description of decorators in the importer.py file.
310            # We just strip off the expr_decorator now since we don't
311            # need from this point on.
312            elif key.startswith(expr_decorator):
313                key = key[len(expr_decorator):]
314                # because it had dots into a list so that we can find the
315                # proper variable to modify.
316                key = key.split(dot_decorator)
317                c = cls
318                for item in key[:-1]:
319                    c = getattr(c, item)
320                setattr(c, key[-1], val)
321
322            # default: use normal path (ends up in __setattr__)
323            else:
324                setattr(cls, key, val)
325
326
327    def _isvalue(cls, name):
328        for c in cls._bases:
329            if c._params.has_key(name):
330                return True
331
332        for c in cls._bases:
333            if c._values.has_key(name):
334                return True
335
336        return False
337
338    # generator that iterates across all parameters for this class and
339    # all classes it inherits from
340    def _getparams(cls):
341        params = {}
342        for c in cls._bases:
343            for p,v in c._params.iteritems():
344                if not params.has_key(p):
345                    params[p] = v
346        return params
347
348    # Lookup a parameter description by name in the given class.
349    def _getparam(cls, name, default = AttributeError):
350        for c in cls._bases:
351            if c._params.has_key(name):
352                return c._params[name]
353        if isSubClass(default, Exception):
354            raise default, \
355                  "object '%s' has no attribute '%s'" % (cls.__name__, name)
356        else:
357            return default
358
359    def _hasvalue(cls, name):
360        for c in cls._bases:
361            if c._values.has_key(name):
362                return True
363
364        return False
365
366    def _getvalues(cls):
367        values = {}
368        for i,c in enumerate(cls._bases):
369            for p,v in c._values.iteritems():
370                if not values.has_key(p):
371                    values[p] = v
372            for p,v in c._params.iteritems():
373                if not values.has_key(p) and hasattr(v, 'default'):
374                    try:
375                        v.valid(v.default)
376                    except TypeError:
377                        panic("Invalid default %s for param %s in node %s"
378                              % (v.default,p,cls.__name__))
379                    v = v.default
380                    cls._setvalue(p, v)
381                    values[p] = v
382
383        return values
384
385    def _getvalue(cls, name, default = AttributeError):
386        value = None
387        for c in cls._bases:
388            if c._values.has_key(name):
389                value = c._values[name]
390                break
391        if value is not None:
392            return value
393
394        param = cls._getparam(name, None)
395        if param is not None and hasattr(param, 'default'):
396            param.valid(param.default)
397            value = param.default
398            cls._setvalue(name, value)
399            return value
400
401        if isSubClass(default, Exception):
402            raise default, 'value for %s not found' % name
403        else:
404            return default
405
406    def _setvalue(cls, name, value):
407        cls._values[name] = value
408
409    def __getattr__(cls, attr):
410        if cls._isvalue(attr):
411            return Value(cls, attr)
412
413        if attr == '_cpp_param_decl' and hasattr(cls, 'type'):
414            return cls.type + '*'
415
416        raise AttributeError, \
417              "object '%s' has no attribute '%s'" % (cls.__name__, attr)
418
419    def _set_keyword(cls, keyword, val, kwtype):
420        if not isinstance(val, kwtype):
421            raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
422                  (keyword, type(val), kwtype)
423        if isinstance(val, types.FunctionType):
424            val = classmethod(val)
425        type.__setattr__(cls, keyword, val)
426
427    # Set attribute (called on foo.attr = value when foo is an
428    # instance of class cls).
429    def __setattr__(cls, attr, value):
430        # normal processing for private attributes
431        if attr.startswith('_'):
432            type.__setattr__(cls, attr, value)
433            return
434
435        if cls.keywords.has_key(attr):
436            cls._set_keyword(attr, value, cls.keywords[attr])
437            return
438
439        # must be SimObject param
440        param = cls._getparam(attr, None)
441        if param:
442            # It's ok: set attribute by delegating to 'object' class.
443            # Note the use of param.make_value() to verify/canonicalize
444            # the assigned value
445            try:
446                param.valid(value)
447            except:
448                panic("Error setting param %s.%s to %s\n" % \
449                      (cls.__name__, attr, value))
450            cls._setvalue(attr, value)
451        elif isConfigNode(value) or isSimObjSequence(value):
452            cls._setvalue(attr, value)
453        else:
454            raise AttributeError, \
455                  "Class %s has no parameter %s" % (cls.__name__, attr)
456
457    def add_child(cls, instance, name, child):
458        if isNullPointer(child) or instance.top_child_names.has_key(name):
459            return
460
461        if issequence(child):
462            kid = []
463            for i,c in enumerate(child):
464                n = '%s%d' % (name, i)
465                k = c.instantiate(n, instance)
466
467                instance.children.append(k)
468                instance.child_names[n] = k
469                instance.child_objects[c] = k
470                kid.append(k)
471        else:
472            kid = child.instantiate(name, instance)
473            instance.children.append(kid)
474            instance.child_names[name] = kid
475            instance.child_objects[child] = kid
476
477        instance.top_child_names[name] = kid
478
479    # Print instance info to .ini file.
480    def instantiate(cls, name, parent = None):
481        instance = Node(name, cls, parent, isParamContext(cls))
482
483        if hasattr(cls, 'check'):
484            cls.check()
485
486        for key,value in cls._getvalues().iteritems():
487            if isConfigNode(value):
488                cls.add_child(instance, key, value)
489            if issequence(value):
490                list = [ v for v in value if isConfigNode(v) ]
491                if len(list):
492                    cls.add_child(instance, key, list)
493
494        for pname,param in cls._getparams().iteritems():
495            try:
496                value = cls._getvalue(pname)
497            except:
498                panic('Error getting %s' % pname)
499
500            try:
501                if isConfigNode(value):
502                    value = instance.child_objects[value]
503                elif issequence(value):
504                    v = []
505                    for val in value:
506                        if isConfigNode(val):
507                            v.append(instance.child_objects[val])
508                        else:
509                            v.append(val)
510                    value = v
511
512                p = NodeParam(pname, param, value)
513                instance.params.append(p)
514                instance.param_names[pname] = p
515            except:
516                print 'Exception while evaluating %s.%s' % \
517                      (instance.path, pname)
518                raise
519
520        return instance
521
522    def _convert(cls, value):
523        realvalue = value
524        if isinstance(value, Node):
525            realvalue = value.realtype
526
527        if isinstance(realvalue, Proxy):
528            return value
529
530        if realvalue == None or isNullPointer(realvalue):
531            return value
532
533        if isSubClass(realvalue, cls):
534            return value
535
536        raise TypeError, 'object %s type %s wrong type, should be %s' % \
537              (repr(realvalue), realvalue, cls)
538
539    def _string(cls, value):
540        if isNullPointer(value):
541            return 'Null'
542        return Node._string(value)
543
544# The ConfigNode class is the root of the special hierarchy.  Most of
545# the code in this class deals with the configuration hierarchy itself
546# (parent/child node relationships).
547class ConfigNode(object):
548    # Specify metaclass.  Any class inheriting from ConfigNode will
549    # get this metaclass.
550    __metaclass__ = MetaConfigNode
551
552    def __new__(cls, **kwargs):
553        name = cls.__name__ + ("_%d" % cls._anon_subclass_counter)
554        cls._anon_subclass_counter += 1
555        return cls.__metaclass__(name, (cls, ), kwargs)
556
557class ParamContext(ConfigNode):
558    pass
559
560class MetaSimObject(MetaConfigNode):
561    # init_keywords and keywords are inherited from MetaConfigNode,
562    # with overrides/additions
563    init_keywords = MetaConfigNode.init_keywords
564    init_keywords.update({ 'abstract' : types.BooleanType,
565                           'type' : types.StringType })
566
567    keywords = MetaConfigNode.keywords
568    # no additional keywords
569
570    cpp_classes = []
571
572    # initialization
573    def __init__(cls, name, bases, dict):
574        super(MetaSimObject, cls).__init__(name, bases, dict)
575
576        if hasattr(cls, 'type'):
577            if name == 'SimObject':
578                cls._cpp_base = None
579            elif hasattr(cls._bases[1], 'type'):
580                cls._cpp_base = cls._bases[1].type
581            else:
582                panic("SimObject %s derives from a non-C++ SimObject %s "\
583                      "(no 'type')" % (cls, cls_bases[1].__name__))
584
585            # This class corresponds to a C++ class: put it on the global
586            # list of C++ objects to generate param structs, etc.
587            MetaSimObject.cpp_classes.append(cls)
588
589    def _cpp_decl(cls):
590        name = cls.__name__
591        code = ""
592        code += "\n".join([e.cpp_declare() for e in cls._param_types.values()])
593        code += "\n"
594        param_names = cls._params.keys()
595        param_names.sort()
596        code += "struct Params"
597        if cls._cpp_base:
598            code += " : public %s::Params" % cls._cpp_base
599        code += " {\n    "
600        code += "\n    ".join([cls._params[pname].cpp_decl(pname) \
601                               for pname in param_names])
602        code += "\n};\n"
603        return code
604
605class NodeParam(object):
606    def __init__(self, name, param, value):
607        self.name = name
608        self.param = param
609        self.ptype = param.ptype
610        self.convert = param.convert
611        self.string = param.string
612        self.value = value
613
614class Node(object):
615    all = {}
616    def __init__(self, name, realtype, parent, paramcontext):
617        self.name = name
618        self.realtype = realtype
619        if isSimObject(realtype):
620            self.type = realtype.type
621        else:
622            self.type = None
623        self.parent = parent
624        self.children = []
625        self.child_names = {}
626        self.child_objects = {}
627        self.top_child_names = {}
628        self.params = []
629        self.param_names = {}
630        self.paramcontext = paramcontext
631
632        path = [ self.name ]
633        node = self.parent
634        while node is not None:
635            if node.name != 'root':
636                path.insert(0, node.name)
637            else:
638                assert(node.parent is None)
639            node = node.parent
640        self.path = '.'.join(path)
641
642    def find(self, realtype, path):
643        if not path:
644            if issubclass(self.realtype, realtype):
645                return self, True
646
647            obj = None
648            for child in self.children:
649                if issubclass(child.realtype, realtype):
650                    if obj is not None:
651                        raise AttributeError, \
652                              'Super matched more than one: %s %s' % \
653                              (obj.path, child.path)
654                    obj = child
655            return obj, obj is not None
656
657        try:
658            obj = self
659            for node in path[:-1]:
660                obj = obj.child_names[node]
661
662            last = path[-1]
663            if obj.child_names.has_key(last):
664                value = obj.child_names[last]
665                if issubclass(value.realtype, realtype):
666                    return value, True
667            elif obj.param_names.has_key(last):
668                value = obj.param_names[last]
669                realtype._convert(value.value)
670                return value.value, True
671        except KeyError:
672            pass
673
674        return None, False
675
676    def unproxy(self, ptype, value):
677        if not isinstance(value, Proxy):
678            return value
679
680        if value is None:
681            raise AttributeError, 'Error while fixing up %s' % self.path
682
683        obj = self
684        done = False
685        while not done:
686            if obj is None:
687                raise AttributeError, \
688                      'Parent of %s type %s not found at path %s' \
689                      % (self.name, ptype, value._path)
690            found, done = obj.find(ptype, value._path)
691            if isinstance(found, Proxy):
692                done = False
693            obj = obj.parent
694
695        return found
696
697    def fixup(self):
698        self.all[self.path] = self
699
700        for param in self.params:
701            ptype = param.ptype
702            pval = param.value
703
704            try:
705                if issequence(pval):
706                    param.value = [ self.unproxy(ptype, pv) for pv in pval ]
707                else:
708                    param.value = self.unproxy(ptype, pval)
709            except:
710                print 'Error while fixing up %s:%s' % (self.path, param.name)
711                raise
712
713        for child in self.children:
714            assert(child != self)
715            child.fixup()
716
717    # print type and parameter values to .ini file
718    def display(self):
719        print '[' + self.path + ']'	# .ini section header
720
721        if isSimObject(self.realtype):
722            print 'type = %s' % self.type
723
724        if self.children:
725            # instantiate children in same order they were added for
726            # backward compatibility (else we can end up with cpu1
727            # before cpu0).  Changing ordering can also influence timing
728            # in the current memory system, as caches get added to a bus
729            # in different orders which affects their priority in the
730            # case of simulataneous requests.  We should uncomment the
731            # following line once we take care of that issue.
732            # self.children.sort(lambda x,y: cmp(x.name, y.name))
733            children = [ c.name for c in self.children if not c.paramcontext]
734            print 'children =', ' '.join(children)
735
736        self.params.sort(lambda x,y: cmp(x.name, y.name))
737        for param in self.params:
738            try:
739                if param.value is None:
740                    raise AttributeError, 'Parameter with no value'
741
742                value = param.convert(param.value)
743                string = param.string(value)
744            except:
745                print 'exception in %s:%s' % (self.path, param.name)
746                raise
747
748            print '%s = %s' % (param.name, string)
749
750        print
751
752        # recursively dump out children
753        for c in self.children:
754            c.display()
755
756    # print type and parameter values to .ini file
757    def outputDot(self, dot):
758
759
760        label = "{%s|" % self.path
761        if isSimObject(self.realtype):
762            label +=  '%s|' % self.type
763
764        if self.children:
765            # instantiate children in same order they were added for
766            # backward compatibility (else we can end up with cpu1
767            # before cpu0).
768            for c in self.children:
769                dot.add_edge(pydot.Edge(self.path,c.path, style="bold"))
770
771        simobjs = []
772        for param in self.params:
773            try:
774                if param.value is None:
775                    raise AttributeError, 'Parameter with no value'
776
777                value = param.convert(param.value)
778                string = param.string(value)
779            except:
780                print 'exception in %s:%s' % (self.name, param.name)
781                raise
782            if isConfigNode(param.ptype) and string != "Null":
783                simobjs.append(string)
784            else:
785                label += '%s = %s\\n' % (param.name, string)
786
787        for so in simobjs:
788            label += "|<%s> %s" % (so, so)
789            dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, tailport="w"))
790        label += '}'
791        dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label))
792
793        # recursively dump out children
794        for c in self.children:
795            c.outputDot(dot)
796
797    def _string(cls, value):
798        if not isinstance(value, Node):
799            raise AttributeError, 'expecting %s got %s' % (Node, value)
800        return value.path
801    _string = classmethod(_string)
802
803#####################################################################
804#
805# Parameter description classes
806#
807# The _params dictionary in each class maps parameter names to
808# either a Param or a VectorParam object.  These objects contain the
809# parameter description string, the parameter type, and the default
810# value (loaded from the PARAM section of the .odesc files).  The
811# _convert() method on these objects is used to force whatever value
812# is assigned to the parameter to the appropriate type.
813#
814# Note that the default values are loaded into the class's attribute
815# space when the parameter dictionary is initialized (in
816# MetaConfigNode._setparams()); after that point they aren't used.
817#
818#####################################################################
819
820def isNullPointer(value):
821    return isinstance(value, NullSimObject)
822
823class Value(object):
824    def __init__(self, obj, attr):
825        super(Value, self).__setattr__('attr', attr)
826        super(Value, self).__setattr__('obj', obj)
827
828    def _getattr(self):
829        return self.obj._getvalue(self.attr)
830
831    def __setattr__(self, attr, value):
832        setattr(self._getattr(), attr, value)
833
834    def __getattr__(self, attr):
835        return getattr(self._getattr(), attr)
836
837    def __getitem__(self, index):
838        return self._getattr().__getitem__(index)
839
840    def __call__(self, *args, **kwargs):
841        return self._getattr().__call__(*args, **kwargs)
842
843    def __nonzero__(self):
844        return bool(self._getattr())
845
846    def __str__(self):
847        return str(self._getattr())
848
849# Regular parameter.
850class _Param(object):
851    def __init__(self, ptype, *args, **kwargs):
852        if isinstance(ptype, types.StringType):
853            self.ptype_string = ptype
854        elif isinstance(ptype, type):
855            self.ptype = ptype
856        else:
857            raise TypeError, "Param type is not a type (%s)" % ptype
858
859        if args:
860            if len(args) == 1:
861                self.desc = args[0]
862            elif len(args) == 2:
863                self.default = args[0]
864                self.desc = args[1]
865            else:
866                raise TypeError, 'too many arguments'
867
868        if kwargs.has_key('desc'):
869            assert(not hasattr(self, 'desc'))
870            self.desc = kwargs['desc']
871            del kwargs['desc']
872
873        if kwargs.has_key('default'):
874            assert(not hasattr(self, 'default'))
875            self.default = kwargs['default']
876            del kwargs['default']
877
878        if kwargs:
879            raise TypeError, 'extra unknown kwargs %s' % kwargs
880
881        if not hasattr(self, 'desc'):
882            raise TypeError, 'desc attribute missing'
883
884    def maybe_resolve_type(self, context):
885        # check if already resolved... don't use hasattr(),
886        # as that calls __getattr__()
887        if self.__dict__.has_key('ptype'):
888            return
889        try:
890            self.ptype = context[self.ptype_string]
891        except KeyError:
892            # no harm in trying... we'll try again later using global scope
893            pass
894
895    def __getattr__(self, attr):
896        if attr == 'ptype':
897            try:
898                self.ptype = param_types[self.ptype_string]
899                return self.ptype
900            except:
901                panic("undefined Param type %s" % self.ptype_string)
902        else:
903            raise AttributeError, "'%s' object has no attribute '%s'" % \
904                  (type(self).__name__, attr)
905
906    def valid(self, value):
907        if not isinstance(value, Proxy):
908            self.ptype._convert(value)
909
910    def convert(self, value):
911        return self.ptype._convert(value)
912
913    def string(self, value):
914        return self.ptype._string(value)
915
916    def set(self, name, instance, value):
917        instance.__dict__[name] = value
918
919    def cpp_decl(self, name):
920        return '%s %s;' % (self.ptype._cpp_param_decl, name)
921
922class _ParamProxy(object):
923    def __init__(self, type):
924        self.ptype = type
925
926    # E.g., Param.Int(5, "number of widgets")
927    def __call__(self, *args, **kwargs):
928        return _Param(self.ptype, *args, **kwargs)
929
930    # Strange magic to theoretically allow dotted names as Param classes,
931    # e.g., Param.Foo.Bar(...) to have a param of type Foo.Bar
932    def __getattr__(self, attr):
933        if attr == '__bases__':
934            raise AttributeError, ''
935        cls = type(self)
936        return cls(attr)
937
938    def __setattr__(self, attr, value):
939        if attr != 'ptype':
940            raise AttributeError, \
941                  'Attribute %s not available in %s' % (attr, self.__class__)
942        super(_ParamProxy, self).__setattr__(attr, value)
943
944
945Param = _ParamProxy(None)
946
947# Vector-valued parameter description.  Just like Param, except that
948# the value is a vector (list) of the specified type instead of a
949# single value.
950class _VectorParam(_Param):
951    def __init__(self, type, *args, **kwargs):
952        _Param.__init__(self, type, *args, **kwargs)
953
954    def valid(self, value):
955        if value == None:
956            return True
957
958        if issequence(value):
959            for val in value:
960                if not isinstance(val, Proxy):
961                    self.ptype._convert(val)
962        elif not isinstance(value, Proxy):
963            self.ptype._convert(value)
964
965    # Convert assigned value to appropriate type.  If the RHS is not a
966    # list or tuple, it generates a single-element list.
967    def convert(self, value):
968        if value == None:
969            return []
970
971        if issequence(value):
972            # list: coerce each element into new list
973            return [ self.ptype._convert(v) for v in value ]
974        else:
975            # singleton: coerce & wrap in a list
976            return self.ptype._convert(value)
977
978    def string(self, value):
979        if issequence(value):
980            return ' '.join([ self.ptype._string(v) for v in value])
981        else:
982            return self.ptype._string(value)
983
984    def cpp_decl(self, name):
985        return 'std::vector<%s> %s;' % (self.ptype._cpp_param_decl, name)
986
987class _VectorParamProxy(_ParamProxy):
988    # E.g., VectorParam.Int(5, "number of widgets")
989    def __call__(self, *args, **kwargs):
990        return _VectorParam(self.ptype, *args, **kwargs)
991
992VectorParam = _VectorParamProxy(None)
993
994#####################################################################
995#
996# Parameter Types
997#
998# Though native Python types could be used to specify parameter types
999# (the 'ptype' field of the Param and VectorParam classes), it's more
1000# flexible to define our own set of types.  This gives us more control
1001# over how Python expressions are converted to values (via the
1002# __init__() constructor) and how these values are printed out (via
1003# the __str__() conversion method).  Eventually we'll need these types
1004# to correspond to distinct C++ types as well.
1005#
1006#####################################################################
1007
1008
1009# Metaclass for bounds-checked integer parameters.  See CheckedInt.
1010class CheckedIntType(type):
1011    def __init__(cls, name, bases, dict):
1012        super(CheckedIntType, cls).__init__(name, bases, dict)
1013
1014        # CheckedInt is an abstract base class, so we actually don't
1015        # want to do any processing on it... the rest of this code is
1016        # just for classes that derive from CheckedInt.
1017        if name == 'CheckedInt':
1018            return
1019
1020        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
1021            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
1022                panic("CheckedInt subclass %s must define either\n" \
1023                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
1024                      % name);
1025            if cls.unsigned:
1026                cls.min = 0
1027                cls.max = 2 ** cls.size - 1
1028            else:
1029                cls.min = -(2 ** (cls.size - 1))
1030                cls.max = (2 ** (cls.size - 1)) - 1
1031
1032        cls._cpp_param_decl = cls.cppname
1033
1034    def _convert(cls, value):
1035        if isinstance(value, bool):
1036            return int(value)
1037
1038        if not isinstance(value, (int, long, float, str)):
1039            raise TypeError, 'Integer param of invalid type %s' % type(value)
1040
1041        if isinstance(value, (str, float)):
1042            value = long(float(value))
1043
1044        if not cls.min <= value <= cls.max:
1045            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
1046                  (cls.min, value, cls.max)
1047
1048        return value
1049
1050    def _string(cls, value):
1051        return str(value)
1052
1053# Abstract superclass for bounds-checked integer parameters.  This
1054# class is subclassed to generate parameter classes with specific
1055# bounds.  Initialization of the min and max bounds is done in the
1056# metaclass CheckedIntType.__init__.
1057class CheckedInt(ParamType):
1058    __metaclass__ = CheckedIntType
1059
1060class Int(CheckedInt):      cppname = 'int';      size = 32; unsigned = False
1061class Unsigned(CheckedInt): cppname = 'unsigned'; size = 32; unsigned = True
1062
1063class Int8(CheckedInt):     cppname =  'int8_t';  size =  8; unsigned = False
1064class UInt8(CheckedInt):    cppname = 'uint8_t';  size =  8; unsigned = True
1065class Int16(CheckedInt):    cppname =  'int16_t'; size = 16; unsigned = False
1066class UInt16(CheckedInt):   cppname = 'uint16_t'; size = 16; unsigned = True
1067class Int32(CheckedInt):    cppname =  'int32_t'; size = 32; unsigned = False
1068class UInt32(CheckedInt):   cppname = 'uint32_t'; size = 32; unsigned = True
1069class Int64(CheckedInt):    cppname =  'int64_t'; size = 64; unsigned = False
1070class UInt64(CheckedInt):   cppname = 'uint64_t'; size = 64; unsigned = True
1071
1072class Counter(CheckedInt): cppname = 'Counter'; size = 64; unsigned = True
1073class Addr(CheckedInt):    cppname = 'Addr';    size = 64; unsigned = True
1074class Tick(CheckedInt):    cppname = 'Tick';    size = 64; unsigned = True
1075
1076class Percent(CheckedInt): cppname = 'int'; min = 0; max = 100
1077
1078class Pair(object):
1079    def __init__(self, first, second):
1080        self.first = first
1081        self.second = second
1082
1083class MetaRange(type):
1084    def __init__(cls, name, bases, dict):
1085        super(MetaRange, cls).__init__(name, bases, dict)
1086        if name == 'Range':
1087            return
1088        cls._cpp_param_decl = 'Range<%s>' % cls.type._cpp_param_decl
1089
1090    def _convert(cls, value):
1091        if not isinstance(value, Pair):
1092            raise TypeError, 'value %s is not a Pair' % value
1093        return Pair(cls.type._convert(value.first),
1094                    cls.type._convert(value.second))
1095
1096    def _string(cls, value):
1097        return '%s:%s' % (cls.type._string(value.first),
1098                          cls.type._string(value.second))
1099
1100class Range(ParamType):
1101    __metaclass__ = MetaRange
1102
1103def RangeSize(start, size):
1104    return Pair(start, start + size - 1)
1105
1106class AddrRange(Range): type = Addr
1107
1108# Boolean parameter type.
1109class Bool(ParamType):
1110    _cpp_param_decl = 'bool'
1111    def _convert(value):
1112        t = type(value)
1113        if t == bool:
1114            return value
1115
1116        if t == int or t == long:
1117            return bool(value)
1118
1119        if t == str:
1120            v = value.lower()
1121            if v == "true" or v == "t" or v == "yes" or v == "y":
1122                return True
1123            elif v == "false" or v == "f" or v == "no" or v == "n":
1124                return False
1125
1126        raise TypeError, 'Bool parameter (%s) of invalid type %s' % (v, t)
1127    _convert = staticmethod(_convert)
1128
1129    def _string(value):
1130        if value:
1131            return "true"
1132        else:
1133            return "false"
1134    _string = staticmethod(_string)
1135
1136# String-valued parameter.
1137class String(ParamType):
1138    _cpp_param_decl = 'string'
1139
1140    # Constructor.  Value must be Python string.
1141    def _convert(cls,value):
1142        if value is None:
1143            return ''
1144        if isinstance(value, str):
1145            return value
1146
1147        raise TypeError, \
1148              "String param got value %s %s" % (repr(value), type(value))
1149    _convert = classmethod(_convert)
1150
1151    # Generate printable string version.  Not too tricky.
1152    def _string(cls, value):
1153        return value
1154    _string = classmethod(_string)
1155
1156
1157def IncEthernetAddr(addr, val = 1):
1158    bytes = map(lambda x: int(x, 16), addr.split(':'))
1159    bytes[5] += val
1160    for i in (5, 4, 3, 2, 1):
1161        val,rem = divmod(bytes[i], 256)
1162        bytes[i] = rem
1163        if val == 0:
1164            break
1165        bytes[i - 1] += val
1166    assert(bytes[0] <= 255)
1167    return ':'.join(map(lambda x: '%02x' % x, bytes))
1168
1169class NextEthernetAddr(object):
1170    __metaclass__ = Singleton
1171    addr = "00:90:00:00:00:01"
1172
1173    def __init__(self, inc = 1):
1174        self.value = self.addr
1175        self.addr = IncEthernetAddr(self.addr, inc)
1176
1177class EthernetAddr(ParamType):
1178    _cpp_param_decl = 'EthAddr'
1179
1180    def _convert(cls, value):
1181        if value == NextEthernetAddr:
1182            return value
1183
1184        if not isinstance(value, str):
1185            raise TypeError, "expected an ethernet address and didn't get one"
1186
1187        bytes = value.split(':')
1188        if len(bytes) != 6:
1189            raise TypeError, 'invalid ethernet address %s' % value
1190
1191        for byte in bytes:
1192            if not 0 <= int(byte) <= 256:
1193                raise TypeError, 'invalid ethernet address %s' % value
1194
1195        return value
1196    _convert = classmethod(_convert)
1197
1198    def _string(cls, value):
1199        if value == NextEthernetAddr:
1200            value = value().value
1201        return value
1202    _string = classmethod(_string)
1203
1204# Special class for NULL pointers.  Note the special check in
1205# make_param_value() above that lets these be assigned where a
1206# SimObject is required.
1207# only one copy of a particular node
1208class NullSimObject(object):
1209    __metaclass__ = Singleton
1210
1211    def __call__(cls):
1212        return cls
1213
1214    def _instantiate(self, parent = None, path = ''):
1215        pass
1216
1217    def _convert(cls, value):
1218        if value == Nxone:
1219            return
1220
1221        if isinstance(value, cls):
1222            return value
1223
1224        raise TypeError, 'object %s %s of the wrong type, should be %s' % \
1225              (repr(value), type(value), cls)
1226    _convert = classmethod(_convert)
1227
1228    def _string():
1229        return 'NULL'
1230    _string = staticmethod(_string)
1231
1232# The only instance you'll ever need...
1233Null = NULL = NullSimObject()
1234
1235# Enumerated types are a little more complex.  The user specifies the
1236# type as Enum(foo) where foo is either a list or dictionary of
1237# alternatives (typically strings, but not necessarily so).  (In the
1238# long run, the integer value of the parameter will be the list index
1239# or the corresponding dictionary value.  For now, since we only check
1240# that the alternative is valid and then spit it into a .ini file,
1241# there's not much point in using the dictionary.)
1242
1243# What Enum() must do is generate a new type encapsulating the
1244# provided list/dictionary so that specific values of the parameter
1245# can be instances of that type.  We define two hidden internal
1246# classes (_ListEnum and _DictEnum) to serve as base classes, then
1247# derive the new type from the appropriate base class on the fly.
1248
1249
1250# Metaclass for Enum types
1251class MetaEnum(type):
1252
1253    def __init__(cls, name, bases, init_dict):
1254        if init_dict.has_key('map'):
1255            if not isinstance(cls.map, dict):
1256                raise TypeError, "Enum-derived class attribute 'map' " \
1257                      "must be of type dict"
1258            # build list of value strings from map
1259            cls.vals = cls.map.keys()
1260            cls.vals.sort()
1261        elif init_dict.has_key('vals'):
1262            if not isinstance(cls.vals, list):
1263                raise TypeError, "Enum-derived class attribute 'vals' " \
1264                      "must be of type list"
1265            # build string->value map from vals sequence
1266            cls.map = {}
1267            for idx,val in enumerate(cls.vals):
1268                cls.map[val] = idx
1269        else:
1270            raise TypeError, "Enum-derived class must define "\
1271                  "attribute 'map' or 'vals'"
1272
1273        cls._cpp_param_decl = name
1274
1275        super(MetaEnum, cls).__init__(name, bases, init_dict)
1276
1277    def cpp_declare(cls):
1278        s = 'enum %s {\n    ' % cls.__name__
1279        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
1280        s += '\n};\n'
1281        return s
1282
1283# Base class for enum types.
1284class Enum(ParamType):
1285    __metaclass__ = MetaEnum
1286    vals = []
1287
1288    def _convert(self, value):
1289        if value not in self.map:
1290            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1291                  % (value, self.vals)
1292        return value
1293    _convert = classmethod(_convert)
1294
1295    # Generate printable string version of value.
1296    def _string(self, value):
1297        return str(value)
1298    _string = classmethod(_string)
1299#
1300# "Constants"... handy aliases for various values.
1301#
1302
1303# Some memory range specifications use this as a default upper bound.
1304MAX_ADDR = Addr.max
1305MaxTick = Tick.max
1306
1307# For power-of-two sizing, e.g. 64*K gives an integer value 65536.
1308K = 1024
1309M = K*K
1310G = K*M
1311
1312#####################################################################
1313
1314# The final hook to generate .ini files.  Called from configuration
1315# script once config is built.
1316def instantiate(root):
1317    instance = root.instantiate('root')
1318    instance.fixup()
1319    instance.display()
1320    if not noDot:
1321       dot = pydot.Dot()
1322       instance.outputDot(dot)
1323       dot.orientation = "portrait"
1324       dot.size = "8.5,11"
1325       dot.ranksep="equally"
1326       dot.rank="samerank"
1327       dot.write("config.dot")
1328       dot.write_ps("config.ps")
1329
1330# SimObject is a minimal extension of ConfigNode, implementing a
1331# hierarchy node that corresponds to an M5 SimObject.  It prints out a
1332# "type=" line to indicate its SimObject class, prints out the
1333# assigned parameters corresponding to its class, and allows
1334# parameters to be set by keyword in the constructor.  Note that most
1335# of the heavy lifting for the SimObject param handling is done in the
1336# MetaConfigNode metaclass.
1337class SimObject(ConfigNode, ParamType):
1338    __metaclass__ = MetaSimObject
1339    type = 'SimObject'
1340
1341
1342__all__ = ['env',
1343           'ConfigNode', 'SimObject', 'ParamContext', 'Param', 'VectorParam',
1344           'Super', 'Enum',
1345           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1346           'Int32', 'UInt32', 'Int64', 'UInt64',
1347           'Counter', 'Addr', 'Tick', 'Percent',
1348           'Pair', 'RangeSize', 'AddrRange', 'MAX_ADDR', 'NULL', 'K', 'M',
1349           'NextEthernetAddr',
1350           'instantiate']
1351