SimObject.py revision 1585
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=64*K)
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            result, done = obj.find(ptype, self._path)
206            obj = obj.parent
207
208        if isinstance(result, Proxy):
209            result = result.unproxy(obj, ptype)
210
211        return self._mulcheck(result)
212
213    def getindex(obj, index):
214        if index == None:
215            return obj
216        try:
217            obj = obj[index]
218        except TypeError:
219            if index != 0:
220                raise
221            # if index is 0 and item is not subscriptable, just
222            # use item itself (so cpu[0] works on uniprocessors)
223        return obj
224    getindex = staticmethod(getindex)
225
226class ProxyFactory(object):
227    def __getattr__(self, attr):
228        return Proxy(attr)
229
230# global object for handling parent.foo proxies
231parent = ProxyFactory()
232
233def isSubClass(value, cls):
234    try:
235        return issubclass(value, cls)
236    except:
237        return False
238
239def isConfigNode(value):
240    try:
241        return issubclass(value, ConfigNode)
242    except:
243        return False
244
245def isSimObject(value):
246    try:
247        return issubclass(value, SimObject)
248    except:
249        return False
250
251def isSimObjSequence(value):
252    if not isinstance(value, (list, tuple)):
253        return False
254
255    for val in value:
256        if not isNullPointer(val) and not isConfigNode(val):
257            return False
258
259    return True
260
261def isParamContext(value):
262    try:
263        return issubclass(value, ParamContext)
264    except:
265        return False
266
267
268class_decorator = 'M5M5_SIMOBJECT_'
269expr_decorator = 'M5M5_EXPRESSION_'
270dot_decorator = '_M5M5_DOT_'
271
272# 'Global' map of legitimate types for SimObject parameters.
273param_types = {}
274
275# Dummy base class to identify types that are legitimate for SimObject
276# parameters.
277class ParamType(object):
278    pass
279
280# Add types defined in given context (dict or module) that are derived
281# from ParamType to param_types map.
282def add_param_types(ctx):
283    if isinstance(ctx, types.DictType):
284        source_dict = ctx
285    elif isinstance(ctx, types.ModuleType):
286        source_dict = ctx.__dict__
287    else:
288        raise TypeError, \
289              "m5.config.add_param_types requires dict or module as arg"
290    for key,val in source_dict.iteritems():
291        if isinstance(val, type) and issubclass(val, ParamType):
292            param_types[key] = val
293
294# The metaclass for ConfigNode (and thus for everything that derives
295# from ConfigNode, including SimObject).  This class controls how new
296# classes that derive from ConfigNode are instantiated, and provides
297# inherited class behavior (just like a class controls how instances
298# of that class are instantiated, and provides inherited instance
299# behavior).
300class MetaConfigNode(type):
301    # Attributes that can be set only at initialization time
302    init_keywords = {}
303    # Attributes that can be set any time
304    keywords = { 'check' : types.FunctionType,
305                 'children' : types.ListType }
306
307    # __new__ is called before __init__, and is where the statements
308    # in the body of the class definition get loaded into the class's
309    # __dict__.  We intercept this to filter out parameter assignments
310    # and only allow "private" attributes to be passed to the base
311    # __new__ (starting with underscore).
312    def __new__(mcls, name, bases, dict):
313        # Copy "private" attributes (including special methods such as __new__)
314        # to the official dict.  Everything else goes in _init_dict to be
315        # filtered in __init__.
316        cls_dict = {}
317        for key,val in dict.items():
318            if key.startswith('_'):
319                cls_dict[key] = val
320                del dict[key]
321        cls_dict['_init_dict'] = dict
322        return super(MetaConfigNode, mcls).__new__(mcls, name, bases, cls_dict)
323
324    # initialization
325    def __init__(cls, name, bases, dict):
326        super(MetaConfigNode, cls).__init__(name, bases, dict)
327
328        # initialize required attributes
329        cls._params = multidict()
330        cls._values = multidict()
331        cls._param_types = {}
332        cls._bases = [c for c in cls.__mro__ if isConfigNode(c)]
333        cls._anon_subclass_counter = 0
334
335        # We don't support multiple inheritence.  If you want to, you
336        # must fix multidict to deal with it properly.
337        sob = [ base for base in bases \
338                if issubclass(base, ParamType) and base != ParamType ]
339
340        if len(sob) == 1:
341            # If your parent has a value in it that's a config node, clone
342            # it.  Do this now so if we update any of the values'
343            # attributes we are updating the clone and not the original.
344            for key,val in sob[0]._values.iteritems():
345
346                # don't clone if (1) we're about to overwrite it with
347                # a local setting or (2) we've already cloned a copy
348                # from an earlier (more derived) base
349                if cls._init_dict.has_key(key) or cls._values.has_key(key):
350                    continue
351
352                if isConfigNode(val):
353                    cls._values[key] = val()
354                elif isSimObjSequence(val) and len(val):
355                    cls._values[key] = [ v() for v in val ]
356
357            cls._params.parent = sob[0]._params
358            cls._values.parent = sob[0]._values
359
360        elif len(sob) > 1:
361            panic("""\
362The config hierarchy only supports single inheritence of SimObject
363classes. You're trying to derive from:
364%s""" % str(sob))
365
366        # process param types from _init_dict, as these may be needed
367        # by param descriptions also in _init_dict
368        for key,val in cls._init_dict.items():
369            if isinstance(val, type) and issubclass(val, ParamType):
370                cls._param_types[key] = val
371                if not issubclass(val, ConfigNode):
372                    del cls._init_dict[key]
373
374        # now process remaining _init_dict items
375        for key,val in cls._init_dict.items():
376            # param descriptions
377            if isinstance(val, _Param):
378                cls._new_param(key, val)
379
380            # init-time-only keywords
381            elif cls.init_keywords.has_key(key):
382                cls._set_keyword(key, val, cls.init_keywords[key])
383
384            # See description of decorators in the importer.py file.
385            # We just strip off the expr_decorator now since we don't
386            # need from this point on.
387            elif key.startswith(expr_decorator):
388                key = key[len(expr_decorator):]
389                # because it had dots into a list so that we can find the
390                # proper variable to modify.
391                key = key.split(dot_decorator)
392                c = cls
393                for item in key[:-1]:
394                    c = getattr(c, item)
395                setattr(c, key[-1], val)
396
397            # default: use normal path (ends up in __setattr__)
398            else:
399                setattr(cls, key, val)
400
401    def _set_keyword(cls, keyword, val, kwtype):
402        if not isinstance(val, kwtype):
403            raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
404                  (keyword, type(val), kwtype)
405        if isinstance(val, types.FunctionType):
406            val = classmethod(val)
407        type.__setattr__(cls, keyword, val)
408
409    def _new_param(cls, name, value):
410        cls._params[name] = value
411        if hasattr(value, 'default'):
412            cls._values[name] = value.default
413        # try to resolve local param types in local param_types scope
414        value.maybe_resolve_type(cls._param_types)
415
416    # Set attribute (called on foo.attr = value when foo is an
417    # instance of class cls).
418    def __setattr__(cls, attr, value):
419        # normal processing for private attributes
420        if attr.startswith('_'):
421            type.__setattr__(cls, attr, value)
422            return
423
424        if cls.keywords.has_key(attr):
425            cls._set_keyword(attr, value, cls.keywords[attr])
426            return
427
428        # must be SimObject param
429        param = cls._params.get(attr, None)
430        if param:
431            # It's ok: set attribute by delegating to 'object' class.
432            # Note the use of param.make_value() to verify/canonicalize
433            # the assigned value
434            try:
435                param.valid(value)
436            except Exception, e:
437                panic("Exception: %s\nError setting param %s.%s to %s\n" % \
438                      (e, cls.__name__, attr, value))
439            cls._values[attr] = value
440        elif isConfigNode(value) or isSimObjSequence(value):
441            cls._values[attr] = value
442        else:
443            raise AttributeError, \
444                  "Class %s has no parameter %s" % (cls.__name__, attr)
445
446    def __getattr__(cls, attr):
447        if cls._params.has_key(attr) or cls._values.has_key(attr):
448            return Value(cls, attr)
449
450        if attr == '_cpp_param_decl' and hasattr(cls, 'type'):
451            return cls.type + '*'
452
453        raise AttributeError, \
454              "object '%s' has no attribute '%s'" % (cls.__name__, attr)
455
456    def add_child(cls, instance, name, child):
457        if isNullPointer(child) or instance.top_child_names.has_key(name):
458            return
459
460        if isinstance(child, (list, tuple)):
461            kid = []
462            for i,c in enumerate(child):
463                n = '%s%d' % (name, i)
464                k = c.instantiate(n, instance)
465
466                instance.children.append(k)
467                instance.child_names[n] = k
468                instance.child_objects[c] = k
469                kid.append(k)
470        else:
471            kid = child.instantiate(name, instance)
472            instance.children.append(kid)
473            instance.child_names[name] = kid
474            instance.child_objects[child] = kid
475
476        instance.top_child_names[name] = kid
477
478    # Print instance info to .ini file.
479    def instantiate(cls, name, parent = None):
480        instance = Node(name, cls, parent, isParamContext(cls))
481
482        if hasattr(cls, 'check'):
483            cls.check()
484
485        for key,value in cls._values.iteritems():
486            if isConfigNode(value):
487                cls.add_child(instance, key, value)
488            if isinstance(value, (list, tuple)):
489                vals = [ v for v in value if isConfigNode(v) ]
490                if len(vals):
491                    cls.add_child(instance, key, vals)
492
493        for pname,param in cls._params.iteritems():
494            value = cls._values.get(pname, None)
495            if value is None:
496                panic('Error getting %s from %s' % (pname, name))
497
498            try:
499                if isConfigNode(value):
500                    value = instance.child_objects[value]
501                elif isinstance(value, (list, tuple)):
502                    v = []
503                    for val in value:
504                        if isConfigNode(val):
505                            v.append(instance.child_objects[val])
506                        else:
507                            v.append(val)
508                    value = v
509
510                p = NodeParam(pname, param, value)
511                instance.params.append(p)
512                instance.param_names[pname] = p
513            except:
514                print 'Exception while evaluating %s.%s' % \
515                      (instance.path, pname)
516                raise
517
518        return instance
519
520    def _convert(cls, value):
521        realvalue = value
522        if isinstance(value, Node):
523            realvalue = value.realtype
524
525        if isinstance(realvalue, Proxy):
526            return value
527
528        if realvalue == None or isNullPointer(realvalue):
529            return value
530
531        if isSubClass(realvalue, cls):
532            return value
533
534        raise TypeError, 'object %s type %s wrong type, should be %s' % \
535              (repr(realvalue), realvalue, cls)
536
537    def _string(cls, value):
538        if isNullPointer(value):
539            return 'Null'
540        return Node._string(value)
541
542# The ConfigNode class is the root of the special hierarchy.  Most of
543# the code in this class deals with the configuration hierarchy itself
544# (parent/child node relationships).
545class ConfigNode(object):
546    # Specify metaclass.  Any class inheriting from ConfigNode will
547    # get this metaclass.
548    __metaclass__ = MetaConfigNode
549
550    def __new__(cls, **kwargs):
551        name = cls.__name__ + ("_%d" % cls._anon_subclass_counter)
552        cls._anon_subclass_counter += 1
553        return cls.__metaclass__(name, (cls, ), kwargs)
554
555class ParamContext(ConfigNode,ParamType):
556    pass
557
558class MetaSimObject(MetaConfigNode):
559    # init_keywords and keywords are inherited from MetaConfigNode,
560    # with overrides/additions
561    init_keywords = MetaConfigNode.init_keywords
562    init_keywords.update({ 'abstract' : types.BooleanType,
563                           'type' : types.StringType })
564
565    keywords = MetaConfigNode.keywords
566    # no additional keywords
567
568    cpp_classes = []
569
570    # initialization
571    def __init__(cls, name, bases, dict):
572        super(MetaSimObject, cls).__init__(name, bases, dict)
573
574        if hasattr(cls, 'type'):
575            if name == 'SimObject':
576                cls._cpp_base = None
577            elif hasattr(cls._bases[1], 'type'):
578                cls._cpp_base = cls._bases[1].type
579            else:
580                panic("SimObject %s derives from a non-C++ SimObject %s "\
581                      "(no 'type')" % (cls, cls_bases[1].__name__))
582
583            # This class corresponds to a C++ class: put it on the global
584            # list of C++ objects to generate param structs, etc.
585            MetaSimObject.cpp_classes.append(cls)
586
587    def _cpp_decl(cls):
588        name = cls.__name__
589        code = ""
590        code += "\n".join([e.cpp_declare() for e in cls._param_types.values()])
591        code += "\n"
592        param_names = cls._params.keys()
593        param_names.sort()
594        code += "struct Params"
595        if cls._cpp_base:
596            code += " : public %s::Params" % cls._cpp_base
597        code += " {\n    "
598        code += "\n    ".join([cls._params[pname].cpp_decl(pname) \
599                               for pname in param_names])
600        code += "\n};\n"
601        return code
602
603class NodeParam(object):
604    def __init__(self, name, param, value):
605        self.name = name
606        self.param = param
607        self.ptype = param.ptype
608        self.convert = param.convert
609        self.string = param.string
610        self.value = value
611
612class Node(object):
613    all = {}
614    def __init__(self, name, realtype, parent, paramcontext):
615        self.name = name
616        self.realtype = realtype
617        if isSimObject(realtype):
618            self.type = realtype.type
619        else:
620            self.type = None
621        self.parent = parent
622        self.children = []
623        self.child_names = {}
624        self.child_objects = {}
625        self.top_child_names = {}
626        self.params = []
627        self.param_names = {}
628        self.paramcontext = paramcontext
629
630        path = [ self.name ]
631        node = self.parent
632        while node is not None:
633            if node.name != 'root':
634                path.insert(0, node.name)
635            else:
636                assert(node.parent is None)
637            node = node.parent
638        self.path = '.'.join(path)
639
640    def find(self, realtype, path):
641        if not path:
642            if issubclass(self.realtype, realtype):
643                return self, True
644
645            obj = None
646            for child in self.children:
647                if issubclass(child.realtype, realtype):
648                    if obj is not None:
649                        raise AttributeError, \
650                              'parent.any matched more than one: %s %s' % \
651                              (obj.path, child.path)
652                    obj = child
653            return obj, obj is not None
654
655        try:
656            obj = self
657            for (node,index) in path[:-1]:
658                if obj.child_names.has_key(node):
659                    obj = obj.child_names[node]
660                else:
661                    obj = obj.top_child_names[node]
662                obj = Proxy.getindex(obj, index)
663
664            (last,index) = path[-1]
665            if obj.child_names.has_key(last):
666                value = obj.child_names[last]
667                return Proxy.getindex(value, index), True
668            elif obj.top_child_names.has_key(last):
669                value = obj.top_child_names[last]
670                return Proxy.getindex(value, index), True
671            elif obj.param_names.has_key(last):
672                value = obj.param_names[last]
673                realtype._convert(value.value)
674                return Proxy.getindex(value.value, index), True
675        except KeyError:
676            pass
677
678        return None, False
679
680    def unproxy(self, param, ptype):
681        if not isinstance(param, Proxy):
682            return param
683        return param.unproxy(self, ptype)
684
685    def fixup(self):
686        self.all[self.path] = self
687
688        for param in self.params:
689            ptype = param.ptype
690            pval = param.value
691
692            try:
693                if isinstance(pval, (list, tuple)):
694                    param.value = [ self.unproxy(pv, ptype) for pv in pval ]
695                else:
696                    param.value = self.unproxy(pval, ptype)
697            except:
698                print 'Error while fixing up %s:%s' % (self.path, param.name)
699                raise
700
701        for child in self.children:
702            assert(child != self)
703            child.fixup()
704
705    # print type and parameter values to .ini file
706    def display(self):
707        print '[' + self.path + ']'	# .ini section header
708
709        if isSimObject(self.realtype):
710            print 'type = %s' % self.type
711
712        if self.children:
713            # instantiate children in same order they were added for
714            # backward compatibility (else we can end up with cpu1
715            # before cpu0).  Changing ordering can also influence timing
716            # in the current memory system, as caches get added to a bus
717            # in different orders which affects their priority in the
718            # case of simulataneous requests.
719            self.children.sort(lambda x,y: cmp(x.name, y.name))
720            children = [ c.name for c in self.children if not c.paramcontext]
721            print 'children =', ' '.join(children)
722
723        self.params.sort(lambda x,y: cmp(x.name, y.name))
724        for param in self.params:
725            try:
726                if param.value is None:
727                    raise AttributeError, 'Parameter with no value'
728
729                value = param.convert(param.value)
730                string = param.string(value)
731            except:
732                print 'exception in %s:%s' % (self.path, param.name)
733                raise
734
735            print '%s = %s' % (param.name, string)
736
737        print
738
739        # recursively dump out children
740        for c in self.children:
741            c.display()
742
743    # print type and parameter values to .ini file
744    def outputDot(self, dot):
745
746
747        label = "{%s|" % self.path
748        if isSimObject(self.realtype):
749            label +=  '%s|' % self.type
750
751        if self.children:
752            # instantiate children in same order they were added for
753            # backward compatibility (else we can end up with cpu1
754            # before cpu0).
755            for c in self.children:
756                dot.add_edge(pydot.Edge(self.path,c.path, style="bold"))
757
758        simobjs = []
759        for param in self.params:
760            try:
761                if param.value is None:
762                    raise AttributeError, 'Parameter with no value'
763
764                value = param.convert(param.value)
765                string = param.string(value)
766            except:
767                print 'exception in %s:%s' % (self.name, param.name)
768                raise
769            if isConfigNode(param.ptype) and string != "Null":
770                simobjs.append(string)
771            else:
772                label += '%s = %s\\n' % (param.name, string)
773
774        for so in simobjs:
775            label += "|<%s> %s" % (so, so)
776            dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, 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 _Param(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 _ParamProxy(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 _Param(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(_ParamProxy, self).__setattr__(attr, value)
933
934
935Param = _ParamProxy(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 _VectorParam(_Param):
941    def __init__(self, type, *args, **kwargs):
942        _Param.__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 _VectorParamProxy(_ParamProxy):
978    # E.g., VectorParam.Int(5, "number of widgets")
979    def __call__(self, *args, **kwargs):
980        return _VectorParam(self.ptype, *args, **kwargs)
981
982VectorParam = _VectorParamProxy(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
998
999# Metaclass for bounds-checked integer parameters.  See CheckedInt.
1000class CheckedIntType(type):
1001    def __init__(cls, name, bases, dict):
1002        super(CheckedIntType, cls).__init__(name, bases, dict)
1003
1004        # CheckedInt is an abstract base class, so we actually don't
1005        # want to do any processing on it... the rest of this code is
1006        # just for classes that derive from CheckedInt.
1007        if name == 'CheckedInt':
1008            return
1009
1010        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
1011            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
1012                panic("CheckedInt subclass %s must define either\n" \
1013                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
1014                      % name);
1015            if cls.unsigned:
1016                cls.min = 0
1017                cls.max = 2 ** cls.size - 1
1018            else:
1019                cls.min = -(2 ** (cls.size - 1))
1020                cls.max = (2 ** (cls.size - 1)) - 1
1021
1022        cls._cpp_param_decl = cls.cppname
1023
1024    def _convert(cls, value):
1025        if isinstance(value, bool):
1026            return int(value)
1027
1028        if not isinstance(value, (int, long, float, str)):
1029            raise TypeError, 'Integer param of invalid type %s' % type(value)
1030
1031        if isinstance(value, (str, float)):
1032            value = long(float(value))
1033
1034        if not cls.min <= value <= cls.max:
1035            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
1036                  (cls.min, value, cls.max)
1037
1038        return value
1039
1040    def _string(cls, value):
1041        return str(value)
1042
1043# Abstract superclass for bounds-checked integer parameters.  This
1044# class is subclassed to generate parameter classes with specific
1045# bounds.  Initialization of the min and max bounds is done in the
1046# metaclass CheckedIntType.__init__.
1047class CheckedInt(ParamType):
1048    __metaclass__ = CheckedIntType
1049
1050class Int(CheckedInt):      cppname = 'int';      size = 32; unsigned = False
1051class Unsigned(CheckedInt): cppname = 'unsigned'; size = 32; unsigned = True
1052
1053class Int8(CheckedInt):     cppname =  'int8_t';  size =  8; unsigned = False
1054class UInt8(CheckedInt):    cppname = 'uint8_t';  size =  8; unsigned = True
1055class Int16(CheckedInt):    cppname =  'int16_t'; size = 16; unsigned = False
1056class UInt16(CheckedInt):   cppname = 'uint16_t'; size = 16; unsigned = True
1057class Int32(CheckedInt):    cppname =  'int32_t'; size = 32; unsigned = False
1058class UInt32(CheckedInt):   cppname = 'uint32_t'; size = 32; unsigned = True
1059class Int64(CheckedInt):    cppname =  'int64_t'; size = 64; unsigned = False
1060class UInt64(CheckedInt):   cppname = 'uint64_t'; size = 64; unsigned = True
1061
1062class Counter(CheckedInt): cppname = 'Counter'; size = 64; unsigned = True
1063class Addr(CheckedInt):    cppname = 'Addr';    size = 64; unsigned = True
1064class Tick(CheckedInt):    cppname = 'Tick';    size = 64; unsigned = True
1065
1066class Percent(CheckedInt): cppname = 'int'; min = 0; max = 100
1067
1068class Pair(object):
1069    def __init__(self, first, second):
1070        self.first = first
1071        self.second = second
1072
1073class MetaRange(type):
1074    def __init__(cls, name, bases, dict):
1075        super(MetaRange, cls).__init__(name, bases, dict)
1076        if name == 'Range':
1077            return
1078        cls._cpp_param_decl = 'Range<%s>' % cls.type._cpp_param_decl
1079
1080    def _convert(cls, value):
1081        if not isinstance(value, Pair):
1082            raise TypeError, 'value %s is not a Pair' % value
1083        return Pair(cls.type._convert(value.first),
1084                    cls.type._convert(value.second))
1085
1086    def _string(cls, value):
1087        return '%s:%s' % (cls.type._string(value.first),
1088                          cls.type._string(value.second))
1089
1090class Range(ParamType):
1091    __metaclass__ = MetaRange
1092
1093def RangeSize(start, size):
1094    return Pair(start, start + size - 1)
1095
1096class AddrRange(Range): type = Addr
1097
1098# Boolean parameter type.
1099class Bool(ParamType):
1100    _cpp_param_decl = 'bool'
1101    def _convert(value):
1102        t = type(value)
1103        if t == bool:
1104            return value
1105
1106        if t == int or t == long:
1107            return bool(value)
1108
1109        if t == str:
1110            v = value.lower()
1111            if v == "true" or v == "t" or v == "yes" or v == "y":
1112                return True
1113            elif v == "false" or v == "f" or v == "no" or v == "n":
1114                return False
1115
1116        raise TypeError, 'Bool parameter (%s) of invalid type %s' % (v, t)
1117    _convert = staticmethod(_convert)
1118
1119    def _string(value):
1120        if value:
1121            return "true"
1122        else:
1123            return "false"
1124    _string = staticmethod(_string)
1125
1126# String-valued parameter.
1127class String(ParamType):
1128    _cpp_param_decl = 'string'
1129
1130    # Constructor.  Value must be Python string.
1131    def _convert(cls,value):
1132        if value is None:
1133            return ''
1134        if isinstance(value, str):
1135            return value
1136
1137        raise TypeError, \
1138              "String param got value %s %s" % (repr(value), type(value))
1139    _convert = classmethod(_convert)
1140
1141    # Generate printable string version.  Not too tricky.
1142    def _string(cls, value):
1143        return value
1144    _string = classmethod(_string)
1145
1146
1147def IncEthernetAddr(addr, val = 1):
1148    bytes = map(lambda x: int(x, 16), addr.split(':'))
1149    bytes[5] += val
1150    for i in (5, 4, 3, 2, 1):
1151        val,rem = divmod(bytes[i], 256)
1152        bytes[i] = rem
1153        if val == 0:
1154            break
1155        bytes[i - 1] += val
1156    assert(bytes[0] <= 255)
1157    return ':'.join(map(lambda x: '%02x' % x, bytes))
1158
1159class NextEthernetAddr(object):
1160    __metaclass__ = Singleton
1161    addr = "00:90:00:00:00:01"
1162
1163    def __init__(self, inc = 1):
1164        self.value = self.addr
1165        self.addr = IncEthernetAddr(self.addr, inc)
1166
1167class EthernetAddr(ParamType):
1168    _cpp_param_decl = 'EthAddr'
1169
1170    def _convert(cls, value):
1171        if value == NextEthernetAddr:
1172            return value
1173
1174        if not isinstance(value, str):
1175            raise TypeError, "expected an ethernet address and didn't get one"
1176
1177        bytes = value.split(':')
1178        if len(bytes) != 6:
1179            raise TypeError, 'invalid ethernet address %s' % value
1180
1181        for byte in bytes:
1182            if not 0 <= int(byte) <= 256:
1183                raise TypeError, 'invalid ethernet address %s' % value
1184
1185        return value
1186    _convert = classmethod(_convert)
1187
1188    def _string(cls, value):
1189        if value == NextEthernetAddr:
1190            value = value().value
1191        return value
1192    _string = classmethod(_string)
1193
1194# Special class for NULL pointers.  Note the special check in
1195# make_param_value() above that lets these be assigned where a
1196# SimObject is required.
1197# only one copy of a particular node
1198class NullSimObject(object):
1199    __metaclass__ = Singleton
1200
1201    def __call__(cls):
1202        return cls
1203
1204    def _instantiate(self, parent = None, path = ''):
1205        pass
1206
1207    def _convert(cls, value):
1208        if value == Nxone:
1209            return
1210
1211        if isinstance(value, cls):
1212            return value
1213
1214        raise TypeError, 'object %s %s of the wrong type, should be %s' % \
1215              (repr(value), type(value), cls)
1216    _convert = classmethod(_convert)
1217
1218    def _string():
1219        return 'NULL'
1220    _string = staticmethod(_string)
1221
1222# The only instance you'll ever need...
1223Null = NULL = NullSimObject()
1224
1225# Enumerated types are a little more complex.  The user specifies the
1226# type as Enum(foo) where foo is either a list or dictionary of
1227# alternatives (typically strings, but not necessarily so).  (In the
1228# long run, the integer value of the parameter will be the list index
1229# or the corresponding dictionary value.  For now, since we only check
1230# that the alternative is valid and then spit it into a .ini file,
1231# there's not much point in using the dictionary.)
1232
1233# What Enum() must do is generate a new type encapsulating the
1234# provided list/dictionary so that specific values of the parameter
1235# can be instances of that type.  We define two hidden internal
1236# classes (_ListEnum and _DictEnum) to serve as base classes, then
1237# derive the new type from the appropriate base class on the fly.
1238
1239
1240# Metaclass for Enum types
1241class MetaEnum(type):
1242
1243    def __init__(cls, name, bases, init_dict):
1244        if init_dict.has_key('map'):
1245            if not isinstance(cls.map, dict):
1246                raise TypeError, "Enum-derived class attribute 'map' " \
1247                      "must be of type dict"
1248            # build list of value strings from map
1249            cls.vals = cls.map.keys()
1250            cls.vals.sort()
1251        elif init_dict.has_key('vals'):
1252            if not isinstance(cls.vals, list):
1253                raise TypeError, "Enum-derived class attribute 'vals' " \
1254                      "must be of type list"
1255            # build string->value map from vals sequence
1256            cls.map = {}
1257            for idx,val in enumerate(cls.vals):
1258                cls.map[val] = idx
1259        else:
1260            raise TypeError, "Enum-derived class must define "\
1261                  "attribute 'map' or 'vals'"
1262
1263        cls._cpp_param_decl = name
1264
1265        super(MetaEnum, cls).__init__(name, bases, init_dict)
1266
1267    def cpp_declare(cls):
1268        s = 'enum %s {\n    ' % cls.__name__
1269        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
1270        s += '\n};\n'
1271        return s
1272
1273# Base class for enum types.
1274class Enum(ParamType):
1275    __metaclass__ = MetaEnum
1276    vals = []
1277
1278    def _convert(self, value):
1279        if value not in self.map:
1280            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1281                  % (value, self.vals)
1282        return value
1283    _convert = classmethod(_convert)
1284
1285    # Generate printable string version of value.
1286    def _string(self, value):
1287        return str(value)
1288    _string = classmethod(_string)
1289#
1290# "Constants"... handy aliases for various values.
1291#
1292
1293# Some memory range specifications use this as a default upper bound.
1294MAX_ADDR = Addr.max
1295MaxTick = Tick.max
1296
1297# For power-of-two sizing, e.g. 64*K gives an integer value 65536.
1298K = 1024
1299M = K*K
1300G = K*M
1301
1302#####################################################################
1303
1304# The final hook to generate .ini files.  Called from configuration
1305# script once config is built.
1306def instantiate(root):
1307    instance = root.instantiate('root')
1308    instance.fixup()
1309    instance.display()
1310    if not noDot:
1311       dot = pydot.Dot()
1312       instance.outputDot(dot)
1313       dot.orientation = "portrait"
1314       dot.size = "8.5,11"
1315       dot.ranksep="equally"
1316       dot.rank="samerank"
1317       dot.write("config.dot")
1318       dot.write_ps("config.ps")
1319
1320# SimObject is a minimal extension of ConfigNode, implementing a
1321# hierarchy node that corresponds to an M5 SimObject.  It prints out a
1322# "type=" line to indicate its SimObject class, prints out the
1323# assigned parameters corresponding to its class, and allows
1324# parameters to be set by keyword in the constructor.  Note that most
1325# of the heavy lifting for the SimObject param handling is done in the
1326# MetaConfigNode metaclass.
1327class SimObject(ConfigNode, ParamType):
1328    __metaclass__ = MetaSimObject
1329    type = 'SimObject'
1330
1331
1332# __all__ defines the list of symbols that get exported when
1333# 'from config import *' is invoked.  Try to keep this reasonably
1334# short to avoid polluting other namespaces.
1335__all__ = ['ConfigNode', 'SimObject', 'ParamContext', 'Param', 'VectorParam',
1336           'parent', 'Enum',
1337           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1338           'Int32', 'UInt32', 'Int64', 'UInt64',
1339           'Counter', 'Addr', 'Tick', 'Percent',
1340           'Pair', 'RangeSize', 'AddrRange', 'MAX_ADDR', 'NULL', 'K', 'M',
1341           'NextEthernetAddr',
1342           'instantiate']
1343