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