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