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