SimObject.py revision 2712
1# Copyright (c) 2004-2005 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#
27# Authors: Steve Reinhardt
28#          Nathan Binkert
29
30import os, re, sys, types, inspect
31
32import m5
33from m5 import panic
34from convert import *
35from multidict import multidict
36
37noDot = False
38try:
39    import pydot
40except:
41    noDot = True
42
43class Singleton(type):
44    def __call__(cls, *args, **kwargs):
45        if hasattr(cls, '_instance'):
46            return cls._instance
47
48        cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
49        return cls._instance
50
51#####################################################################
52#
53# M5 Python Configuration Utility
54#
55# The basic idea is to write simple Python programs that build Python
56# objects corresponding to M5 SimObjects for the desired simulation
57# configuration.  For now, the Python emits a .ini file that can be
58# parsed by M5.  In the future, some tighter integration between M5
59# and the Python interpreter may allow bypassing the .ini file.
60#
61# Each SimObject class in M5 is represented by a Python class with the
62# same name.  The Python inheritance tree mirrors the M5 C++ tree
63# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
64# SimObjects inherit from a single SimObject base class).  To specify
65# an instance of an M5 SimObject in a configuration, the user simply
66# instantiates the corresponding Python object.  The parameters for
67# that SimObject are given by assigning to attributes of the Python
68# object, either using keyword assignment in the constructor or in
69# separate assignment statements.  For example:
70#
71# cache = BaseCache(size='64KB')
72# cache.hit_latency = 3
73# cache.assoc = 8
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# Once a set of Python objects have been instantiated in a hierarchy,
86# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
87# will generate a .ini file.  See simple-4cpu.py for an example
88# (corresponding to m5-test/simple-4cpu.ini).
89#
90#####################################################################
91
92#####################################################################
93#
94# ConfigNode/SimObject classes
95#
96# The Python class hierarchy rooted by ConfigNode (which is the base
97# class of SimObject, which in turn is the base class of all other M5
98# SimObject classes) has special attribute behavior.  In general, an
99# object in this hierarchy has three categories of attribute-like
100# things:
101#
102# 1. Regular Python methods and variables.  These must start with an
103# underscore to be treated normally.
104#
105# 2. SimObject parameters.  These values are stored as normal Python
106# attributes, but all assignments to these attributes are checked
107# against the pre-defined set of parameters stored in the class's
108# _params dictionary.  Assignments to attributes that do not
109# correspond to predefined parameters, or that are not of the correct
110# type, incur runtime errors.
111#
112# 3. Hierarchy children.  The child nodes of a ConfigNode are stored
113# in the node's _children dictionary, but can be accessed using the
114# Python attribute dot-notation (just as they are printed out by the
115# simulator).  Children cannot be created using attribute assigment;
116# they must be added by specifying the parent node in the child's
117# constructor or using the '+=' operator.
118
119# The SimObject parameters are the most complex, for a few reasons.
120# First, both parameter descriptions and parameter values are
121# inherited.  Thus parameter description lookup must go up the
122# inheritance chain like normal attribute lookup, but this behavior
123# must be explicitly coded since the lookup occurs in each class's
124# _params attribute.  Second, because parameter values can be set
125# on SimObject classes (to implement default values), the parameter
126# checking behavior must be enforced on class attribute assignments as
127# well as instance attribute assignments.  Finally, because we allow
128# class specialization via inheritance (e.g., see the L1Cache class in
129# the simple-4cpu.py example), we must do parameter checking even on
130# class instantiation.  To provide all these features, we use a
131# metaclass to define most of the SimObject parameter behavior for
132# this class hierarchy.
133#
134#####################################################################
135
136def isSimObject(value):
137    return isinstance(value, SimObject)
138
139def isSimObjectClass(value):
140    try:
141        return issubclass(value, SimObject)
142    except TypeError:
143        # happens if value is not a class at all
144        return False
145
146def isSimObjSequence(value):
147    if not isinstance(value, (list, tuple)):
148        return False
149
150    for val in value:
151        if not isNullPointer(val) and not isSimObject(val):
152            return False
153
154    return True
155
156def isSimObjClassSequence(value):
157    if not isinstance(value, (list, tuple)):
158        return False
159
160    for val in value:
161        if not isNullPointer(val) and not isSimObjectClass(val):
162            return False
163
164    return True
165
166def isNullPointer(value):
167    return isinstance(value, NullSimObject)
168
169# The metaclass for ConfigNode (and thus for everything that derives
170# from ConfigNode, including SimObject).  This class controls how new
171# classes that derive from ConfigNode are instantiated, and provides
172# inherited class behavior (just like a class controls how instances
173# of that class are instantiated, and provides inherited instance
174# behavior).
175class MetaSimObject(type):
176    # Attributes that can be set only at initialization time
177    init_keywords = { 'abstract' : types.BooleanType,
178                      'type' : types.StringType }
179    # Attributes that can be set any time
180    keywords = { 'check' : types.FunctionType,
181                 'children' : types.ListType }
182
183    # __new__ is called before __init__, and is where the statements
184    # in the body of the class definition get loaded into the class's
185    # __dict__.  We intercept this to filter out parameter assignments
186    # and only allow "private" attributes to be passed to the base
187    # __new__ (starting with underscore).
188    def __new__(mcls, name, bases, dict):
189        if dict.has_key('_init_dict'):
190            # must have been called from makeSubclass() rather than
191            # via Python class declaration; bypass filtering process.
192            cls_dict = dict
193        else:
194            # Copy "private" attributes (including special methods
195            # such as __new__) to the official dict.  Everything else
196            # goes in _init_dict to be filtered in __init__.
197            cls_dict = {}
198            for key,val in dict.items():
199                if key.startswith('_'):
200                    cls_dict[key] = val
201                    del dict[key]
202            cls_dict['_init_dict'] = dict
203        return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
204
205    # subclass initialization
206    def __init__(cls, name, bases, dict):
207        # calls type.__init__()... I think that's a no-op, but leave
208        # it here just in case it's not.
209        super(MetaSimObject, cls).__init__(name, bases, dict)
210
211        # initialize required attributes
212        cls._params = multidict()
213        cls._values = multidict()
214        cls._instantiated = False # really instantiated or subclassed
215        cls._anon_subclass_counter = 0
216
217        # We don't support multiple inheritance.  If you want to, you
218        # must fix multidict to deal with it properly.
219        if len(bases) > 1:
220            raise TypeError, "SimObjects do not support multiple inheritance"
221
222        base = bases[0]
223
224        # the only time the following is not true is when we define
225        # the SimObject class itself
226        if isinstance(base, MetaSimObject):
227            cls._params.parent = base._params
228            cls._values.parent = base._values
229            base._instantiated = True
230
231        # now process the _init_dict items
232        for key,val in cls._init_dict.items():
233            if isinstance(val, (types.FunctionType, types.TypeType)):
234                type.__setattr__(cls, key, val)
235
236            # param descriptions
237            elif isinstance(val, ParamDesc):
238                cls._new_param(key, val)
239
240            # init-time-only keywords
241            elif cls.init_keywords.has_key(key):
242                cls._set_keyword(key, val, cls.init_keywords[key])
243
244            # default: use normal path (ends up in __setattr__)
245            else:
246                setattr(cls, key, val)
247
248        # Pull the deep-copy memoization dict out of the class dict if
249        # it's there...
250        memo = cls.__dict__.get('_memo', {})
251
252        # Handle SimObject values
253        for key,val in cls._values.iteritems():
254            # SimObject instances need to be promoted to classes.
255            # Existing classes should not have any instance values, so
256            # these can only occur at the lowest level dict (the
257            # parameters just being set in this class definition).
258            if isSimObject(val):
259                assert(val == cls._values.local[key])
260                cls._values[key] = val.makeClass(memo)
261            elif isSimObjSequence(val) and len(val):
262                assert(val == cls._values.local[key])
263                cls._values[key] = [ v.makeClass(memo) for v in val ]
264            # SimObject classes need to be subclassed so that
265            # parameters that get set at this level only affect this
266            # level and derivatives.
267            elif isSimObjectClass(val):
268                assert(not cls._values.local.has_key(key))
269                cls._values[key] = val.makeSubclass({}, memo)
270            elif isSimObjClassSequence(val) and len(val):
271                assert(not cls._values.local.has_key(key))
272                cls._values[key] = [ v.makeSubclass({}, memo) for v in val ]
273
274
275    def _set_keyword(cls, keyword, val, kwtype):
276        if not isinstance(val, kwtype):
277            raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
278                  (keyword, type(val), kwtype)
279        if isinstance(val, types.FunctionType):
280            val = classmethod(val)
281        type.__setattr__(cls, keyword, val)
282
283    def _new_param(cls, name, value):
284        cls._params[name] = value
285        if hasattr(value, 'default'):
286            setattr(cls, name, value.default)
287
288    # Set attribute (called on foo.attr = value when foo is an
289    # instance of class cls).
290    def __setattr__(cls, attr, value):
291        # normal processing for private attributes
292        if attr.startswith('_'):
293            type.__setattr__(cls, attr, value)
294            return
295
296        if cls.keywords.has_key(attr):
297            cls._set_keyword(attr, value, cls.keywords[attr])
298            return
299
300        # must be SimObject param
301        param = cls._params.get(attr, None)
302        if param:
303            # It's ok: set attribute by delegating to 'object' class.
304            if (isSimObject(value) or isSimObjSequence(value)) \
305                   and cls._instantiated:
306                raise AttributeError, \
307                  "Cannot set SimObject parameter '%s' after\n" \
308                  "    class %s has been instantiated or subclassed" \
309                  % (attr, cls.__name__)
310            try:
311                cls._values[attr] = param.convert(value)
312            except Exception, e:
313                msg = "%s\nError setting param %s.%s to %s\n" % \
314                      (e, cls.__name__, attr, value)
315                e.args = (msg, )
316                raise
317        # I would love to get rid of this
318        elif isSimObject(value) or isSimObjSequence(value):
319           cls._values[attr] = value
320        else:
321            raise AttributeError, \
322                  "Class %s has no parameter %s" % (cls.__name__, attr)
323
324    def __getattr__(cls, attr):
325        if cls._values.has_key(attr):
326            return cls._values[attr]
327
328        raise AttributeError, \
329              "object '%s' has no attribute '%s'" % (cls.__name__, attr)
330
331    # Create a subclass of this class.  Basically a function interface
332    # to the standard Python class definition mechanism, primarily for
333    # internal use.  'memo' dict param supports "deep copy" (really
334    # "deep subclass") operations... within a given operation,
335    # multiple references to a class should result in a single
336    # subclass object with multiple references to it (as opposed to
337    # mutiple unique subclasses).
338    def makeSubclass(cls, init_dict, memo = {}):
339        subcls = memo.get(cls)
340        if not subcls:
341            name = cls.__name__ + '_' + str(cls._anon_subclass_counter)
342            cls._anon_subclass_counter += 1
343            subcls = MetaSimObject(name, (cls,),
344                                   { '_init_dict': init_dict, '_memo': memo })
345        return subcls
346
347# The ConfigNode class is the root of the special hierarchy.  Most of
348# the code in this class deals with the configuration hierarchy itself
349# (parent/child node relationships).
350class SimObject(object):
351    # Specify metaclass.  Any class inheriting from SimObject will
352    # get this metaclass.
353    __metaclass__ = MetaSimObject
354
355    # __new__ operator allocates new instances of the class.  We
356    # override it here just to support "deep instantiation" operation
357    # via the _memo dict.  When recursively instantiating an object
358    # hierarchy we want to make sure that each class is instantiated
359    # only once, and that if there are multiple references to the same
360    # original class, we end up with the corresponding instantiated
361    # references all pointing to the same instance.
362    def __new__(cls, _memo = None, **kwargs):
363        if _memo is not None and _memo.has_key(cls):
364            # return previously instantiated object
365            assert(len(kwargs) == 0)
366            return _memo[cls]
367        else:
368            # Need a new one... if it needs to be memoized, this will
369            # happen in __init__.  We defer the insertion until then
370            # so __init__ can use the memo dict to tell whether or not
371            # to perform the initialization.
372            return super(SimObject, cls).__new__(cls, **kwargs)
373
374    # Initialize new instance previously allocated by __new__.  For
375    # objects with SimObject-valued params, we need to recursively
376    # instantiate the classes represented by those param values as
377    # well (in a consistent "deep copy"-style fashion; see comment
378    # above).
379    def __init__(self, _memo = None, **kwargs):
380        if _memo is not None:
381            # We're inside a "deep instantiation"
382            assert(isinstance(_memo, dict))
383            assert(len(kwargs) == 0)
384            if _memo.has_key(self.__class__):
385                # __new__ returned an existing, already initialized
386                # instance, so there's nothing to do here
387                assert(_memo[self.__class__] == self)
388                return
389            # no pre-existing object, so remember this one here
390            _memo[self.__class__] = self
391        else:
392            # This is a new top-level instantiation... don't memoize
393            # this objcet, but prepare to memoize any recursively
394            # instantiated objects.
395            _memo = {}
396
397        self.__class__._instantiated = True
398
399        self._children = {}
400        # Inherit parameter values from class using multidict so
401        # individual value settings can be overridden.
402        self._values = multidict(self.__class__._values)
403        # For SimObject-valued parameters, the class should have
404        # classes (not instances) for the values.  We need to
405        # instantiate these classes rather than just inheriting the
406        # class object.
407        for key,val in self.__class__._values.iteritems():
408            if isSimObjectClass(val):
409                setattr(self, key, val(_memo))
410            elif isSimObjClassSequence(val) and len(val):
411                setattr(self, key, [ v(_memo) for v in val ])
412        # apply attribute assignments from keyword args, if any
413        for key,val in kwargs.iteritems():
414            setattr(self, key, val)
415
416    # Use this instance as a template to create a new class.
417    def makeClass(self, memo = {}):
418        cls = memo.get(self)
419        if not cls:
420            cls =  self.__class__.makeSubclass(self._values.local)
421            memo[self] = cls
422        return cls
423
424    # Direct instantiation of instances (cloning) is no longer
425    # allowed; must generate class from instance first.
426    def __call__(self, **kwargs):
427        raise TypeError, "cannot instantiate SimObject; "\
428              "use makeClass() to make class first"
429
430    def __getattr__(self, attr):
431        if self._values.has_key(attr):
432            return self._values[attr]
433
434        raise AttributeError, "object '%s' has no attribute '%s'" \
435              % (self.__class__.__name__, attr)
436
437    # Set attribute (called on foo.attr = value when foo is an
438    # instance of class cls).
439    def __setattr__(self, attr, value):
440        # normal processing for private attributes
441        if attr.startswith('_'):
442            object.__setattr__(self, attr, value)
443            return
444
445        # must be SimObject param
446        param = self._params.get(attr, None)
447        if param:
448            # It's ok: set attribute by delegating to 'object' class.
449            try:
450                value = param.convert(value)
451            except Exception, e:
452                msg = "%s\nError setting param %s.%s to %s\n" % \
453                      (e, self.__class__.__name__, attr, value)
454                e.args = (msg, )
455                raise
456        # I would love to get rid of this
457        elif isSimObject(value) or isSimObjSequence(value):
458            pass
459        else:
460            raise AttributeError, "Class %s has no parameter %s" \
461                  % (self.__class__.__name__, attr)
462
463        # clear out old child with this name, if any
464        self.clear_child(attr)
465
466        if isSimObject(value):
467            value.set_path(self, attr)
468        elif isSimObjSequence(value):
469            value = SimObjVector(value)
470            [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)]
471
472        self._values[attr] = value
473
474    # this hack allows tacking a '[0]' onto parameters that may or may
475    # not be vectors, and always getting the first element (e.g. cpus)
476    def __getitem__(self, key):
477        if key == 0:
478            return self
479        raise TypeError, "Non-zero index '%s' to SimObject" % key
480
481    # clear out children with given name, even if it's a vector
482    def clear_child(self, name):
483        if not self._children.has_key(name):
484            return
485        child = self._children[name]
486        if isinstance(child, SimObjVector):
487            for i in xrange(len(child)):
488                del self._children["s%d" % (name, i)]
489        del self._children[name]
490
491    def add_child(self, name, value):
492        self._children[name] = value
493
494    def set_path(self, parent, name):
495        if not hasattr(self, '_parent'):
496            self._parent = parent
497            self._name = name
498            parent.add_child(name, self)
499
500    def path(self):
501        if not hasattr(self, '_parent'):
502            return 'root'
503        ppath = self._parent.path()
504        if ppath == 'root':
505            return self._name
506        return ppath + "." + self._name
507
508    def __str__(self):
509        return self.path()
510
511    def ini_str(self):
512        return self.path()
513
514    def find_any(self, ptype):
515        if isinstance(self, ptype):
516            return self, True
517
518        found_obj = None
519        for child in self._children.itervalues():
520            if isinstance(child, ptype):
521                if found_obj != None and child != found_obj:
522                    raise AttributeError, \
523                          'parent.any matched more than one: %s %s' % \
524                          (found_obj.path, child.path)
525                found_obj = child
526        # search param space
527        for pname,pdesc in self._params.iteritems():
528            if issubclass(pdesc.ptype, ptype):
529                match_obj = self._values[pname]
530                if found_obj != None and found_obj != match_obj:
531                    raise AttributeError, \
532                          'parent.any matched more than one: %s' % obj.path
533                found_obj = match_obj
534        return found_obj, found_obj != None
535
536    def unproxy(self, base):
537        return self
538
539    def print_ini(self):
540        print '[' + self.path() + ']'	# .ini section header
541
542        if hasattr(self, 'type') and not isinstance(self, ParamContext):
543            print 'type=%s' % self.type
544
545        child_names = self._children.keys()
546        child_names.sort()
547        np_child_names = [c for c in child_names \
548                          if not isinstance(self._children[c], ParamContext)]
549        if len(np_child_names):
550            print 'children=%s' % ' '.join(np_child_names)
551
552        param_names = self._params.keys()
553        param_names.sort()
554        for param in param_names:
555            value = self._values.get(param, None)
556            if value != None:
557                if isproxy(value):
558                    try:
559                        value = value.unproxy(self)
560                    except:
561                        print >> sys.stderr, \
562                              "Error in unproxying param '%s' of %s" % \
563                              (param, self.path())
564                        raise
565                    setattr(self, param, value)
566                print '%s=%s' % (param, self._values[param].ini_str())
567
568        print	# blank line between objects
569
570        for child in child_names:
571            self._children[child].print_ini()
572
573    # generate output file for 'dot' to display as a pretty graph.
574    # this code is currently broken.
575    def outputDot(self, dot):
576        label = "{%s|" % self.path
577        if isSimObject(self.realtype):
578            label +=  '%s|' % self.type
579
580        if self.children:
581            # instantiate children in same order they were added for
582            # backward compatibility (else we can end up with cpu1
583            # before cpu0).
584            for c in self.children:
585                dot.add_edge(pydot.Edge(self.path,c.path, style="bold"))
586
587        simobjs = []
588        for param in self.params:
589            try:
590                if param.value is None:
591                    raise AttributeError, 'Parameter with no value'
592
593                value = param.value
594                string = param.string(value)
595            except Exception, e:
596                msg = 'exception in %s:%s\n%s' % (self.name, param.name, e)
597                e.args = (msg, )
598                raise
599
600            if isSimObject(param.ptype) and string != "Null":
601                simobjs.append(string)
602            else:
603                label += '%s = %s\\n' % (param.name, string)
604
605        for so in simobjs:
606            label += "|<%s> %s" % (so, so)
607            dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so,
608                                    tailport="w"))
609        label += '}'
610        dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label))
611
612        # recursively dump out children
613        for c in self.children:
614            c.outputDot(dot)
615
616class ParamContext(SimObject):
617    pass
618
619#####################################################################
620#
621# Proxy object support.
622#
623#####################################################################
624
625class BaseProxy(object):
626    def __init__(self, search_self, search_up):
627        self._search_self = search_self
628        self._search_up = search_up
629        self._multiplier = None
630
631    def __setattr__(self, attr, value):
632        if not attr.startswith('_'):
633            raise AttributeError, 'cannot set attribute on proxy object'
634        super(BaseProxy, self).__setattr__(attr, value)
635
636    # support multiplying proxies by constants
637    def __mul__(self, other):
638        if not isinstance(other, (int, long, float)):
639            raise TypeError, "Proxy multiplier must be integer"
640        if self._multiplier == None:
641            self._multiplier = other
642        else:
643            # support chained multipliers
644            self._multiplier *= other
645        return self
646
647    __rmul__ = __mul__
648
649    def _mulcheck(self, result):
650        if self._multiplier == None:
651            return result
652        return result * self._multiplier
653
654    def unproxy(self, base):
655        obj = base
656        done = False
657
658        if self._search_self:
659            result, done = self.find(obj)
660
661        if self._search_up:
662            while not done:
663                try: obj = obj._parent
664                except: break
665
666                result, done = self.find(obj)
667
668        if not done:
669            raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \
670                  (self.path(), base.path())
671
672        if isinstance(result, BaseProxy):
673            if result == self:
674                raise RuntimeError, "Cycle in unproxy"
675            result = result.unproxy(obj)
676
677        return self._mulcheck(result)
678
679    def getindex(obj, index):
680        if index == None:
681            return obj
682        try:
683            obj = obj[index]
684        except TypeError:
685            if index != 0:
686                raise
687            # if index is 0 and item is not subscriptable, just
688            # use item itself (so cpu[0] works on uniprocessors)
689        return obj
690    getindex = staticmethod(getindex)
691
692    def set_param_desc(self, pdesc):
693        self._pdesc = pdesc
694
695class AttrProxy(BaseProxy):
696    def __init__(self, search_self, search_up, attr):
697        super(AttrProxy, self).__init__(search_self, search_up)
698        self._attr = attr
699        self._modifiers = []
700
701    def __getattr__(self, attr):
702        # python uses __bases__ internally for inheritance
703        if attr.startswith('_'):
704            return super(AttrProxy, self).__getattr__(self, attr)
705        if hasattr(self, '_pdesc'):
706            raise AttributeError, "Attribute reference on bound proxy"
707        self._modifiers.append(attr)
708        return self
709
710    # support indexing on proxies (e.g., Self.cpu[0])
711    def __getitem__(self, key):
712        if not isinstance(key, int):
713            raise TypeError, "Proxy object requires integer index"
714        self._modifiers.append(key)
715        return self
716
717    def find(self, obj):
718        try:
719            val = getattr(obj, self._attr)
720        except:
721            return None, False
722        while isproxy(val):
723            val = val.unproxy(obj)
724        for m in self._modifiers:
725            if isinstance(m, str):
726                val = getattr(val, m)
727            elif isinstance(m, int):
728                val = val[m]
729            else:
730                assert("Item must be string or integer")
731            while isproxy(val):
732                val = val.unproxy(obj)
733        return val, True
734
735    def path(self):
736        p = self._attr
737        for m in self._modifiers:
738            if isinstance(m, str):
739                p += '.%s' % m
740            elif isinstance(m, int):
741                p += '[%d]' % m
742            else:
743                assert("Item must be string or integer")
744        return p
745
746class AnyProxy(BaseProxy):
747    def find(self, obj):
748        return obj.find_any(self._pdesc.ptype)
749
750    def path(self):
751        return 'any'
752
753def isproxy(obj):
754    if isinstance(obj, (BaseProxy, EthernetAddr)):
755        return True
756    elif isinstance(obj, (list, tuple)):
757        for v in obj:
758            if isproxy(v):
759                return True
760    return False
761
762class ProxyFactory(object):
763    def __init__(self, search_self, search_up):
764        self.search_self = search_self
765        self.search_up = search_up
766
767    def __getattr__(self, attr):
768        if attr == 'any':
769            return AnyProxy(self.search_self, self.search_up)
770        else:
771            return AttrProxy(self.search_self, self.search_up, attr)
772
773# global objects for handling proxies
774Parent = ProxyFactory(search_self = False, search_up = True)
775Self = ProxyFactory(search_self = True, search_up = False)
776
777#####################################################################
778#
779# Parameter description classes
780#
781# The _params dictionary in each class maps parameter names to
782# either a Param or a VectorParam object.  These objects contain the
783# parameter description string, the parameter type, and the default
784# value (loaded from the PARAM section of the .odesc files).  The
785# _convert() method on these objects is used to force whatever value
786# is assigned to the parameter to the appropriate type.
787#
788# Note that the default values are loaded into the class's attribute
789# space when the parameter dictionary is initialized (in
790# MetaConfigNode._setparams()); after that point they aren't used.
791#
792#####################################################################
793
794# Dummy base class to identify types that are legitimate for SimObject
795# parameters.
796class ParamValue(object):
797
798    # default for printing to .ini file is regular string conversion.
799    # will be overridden in some cases
800    def ini_str(self):
801        return str(self)
802
803    # allows us to blithely call unproxy() on things without checking
804    # if they're really proxies or not
805    def unproxy(self, base):
806        return self
807
808# Regular parameter description.
809class ParamDesc(object):
810    def __init__(self, ptype_str, ptype, *args, **kwargs):
811        self.ptype_str = ptype_str
812        # remember ptype only if it is provided
813        if ptype != None:
814            self.ptype = ptype
815
816        if args:
817            if len(args) == 1:
818                self.desc = args[0]
819            elif len(args) == 2:
820                self.default = args[0]
821                self.desc = args[1]
822            else:
823                raise TypeError, 'too many arguments'
824
825        if kwargs.has_key('desc'):
826            assert(not hasattr(self, 'desc'))
827            self.desc = kwargs['desc']
828            del kwargs['desc']
829
830        if kwargs.has_key('default'):
831            assert(not hasattr(self, 'default'))
832            self.default = kwargs['default']
833            del kwargs['default']
834
835        if kwargs:
836            raise TypeError, 'extra unknown kwargs %s' % kwargs
837
838        if not hasattr(self, 'desc'):
839            raise TypeError, 'desc attribute missing'
840
841    def __getattr__(self, attr):
842        if attr == 'ptype':
843            try:
844                ptype = eval(self.ptype_str, m5.objects.__dict__)
845                if not isinstance(ptype, type):
846                    panic("Param qualifier is not a type: %s" % self.ptype)
847                self.ptype = ptype
848                return ptype
849            except NameError:
850                pass
851        raise AttributeError, "'%s' object has no attribute '%s'" % \
852              (type(self).__name__, attr)
853
854    def convert(self, value):
855        if isinstance(value, BaseProxy):
856            value.set_param_desc(self)
857            return value
858        if not hasattr(self, 'ptype') and isNullPointer(value):
859            # deferred evaluation of SimObject; continue to defer if
860            # we're just assigning a null pointer
861            return value
862        if isinstance(value, self.ptype):
863            return value
864        if isNullPointer(value) and issubclass(self.ptype, SimObject):
865            return value
866        return self.ptype(value)
867
868# Vector-valued parameter description.  Just like ParamDesc, except
869# that the value is a vector (list) of the specified type instead of a
870# single value.
871
872class VectorParamValue(list):
873    def ini_str(self):
874        return ' '.join([v.ini_str() for v in self])
875
876    def unproxy(self, base):
877        return [v.unproxy(base) for v in self]
878
879class SimObjVector(VectorParamValue):
880    def print_ini(self):
881        for v in self:
882            v.print_ini()
883
884class VectorParamDesc(ParamDesc):
885    # Convert assigned value to appropriate type.  If the RHS is not a
886    # list or tuple, it generates a single-element list.
887    def convert(self, value):
888        if isinstance(value, (list, tuple)):
889            # list: coerce each element into new list
890            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
891            if isSimObjSequence(tmp_list):
892                return SimObjVector(tmp_list)
893            else:
894                return VectorParamValue(tmp_list)
895        else:
896            # singleton: leave it be (could coerce to a single-element
897            # list here, but for some historical reason we don't...
898            return ParamDesc.convert(self, value)
899
900
901class ParamFactory(object):
902    def __init__(self, param_desc_class, ptype_str = None):
903        self.param_desc_class = param_desc_class
904        self.ptype_str = ptype_str
905
906    def __getattr__(self, attr):
907        if self.ptype_str:
908            attr = self.ptype_str + '.' + attr
909        return ParamFactory(self.param_desc_class, attr)
910
911    # E.g., Param.Int(5, "number of widgets")
912    def __call__(self, *args, **kwargs):
913        caller_frame = inspect.currentframe().f_back
914        ptype = None
915        try:
916            ptype = eval(self.ptype_str,
917                         caller_frame.f_globals, caller_frame.f_locals)
918            if not isinstance(ptype, type):
919                raise TypeError, \
920                      "Param qualifier is not a type: %s" % ptype
921        except NameError:
922            # if name isn't defined yet, assume it's a SimObject, and
923            # try to resolve it later
924            pass
925        return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs)
926
927Param = ParamFactory(ParamDesc)
928VectorParam = ParamFactory(VectorParamDesc)
929
930#####################################################################
931#
932# Parameter Types
933#
934# Though native Python types could be used to specify parameter types
935# (the 'ptype' field of the Param and VectorParam classes), it's more
936# flexible to define our own set of types.  This gives us more control
937# over how Python expressions are converted to values (via the
938# __init__() constructor) and how these values are printed out (via
939# the __str__() conversion method).  Eventually we'll need these types
940# to correspond to distinct C++ types as well.
941#
942#####################################################################
943
944# superclass for "numeric" parameter values, to emulate math
945# operations in a type-safe way.  e.g., a Latency times an int returns
946# a new Latency object.
947class NumericParamValue(ParamValue):
948    def __str__(self):
949        return str(self.value)
950
951    def __float__(self):
952        return float(self.value)
953
954    # hook for bounds checking
955    def _check(self):
956        return
957
958    def __mul__(self, other):
959        newobj = self.__class__(self)
960        newobj.value *= other
961        newobj._check()
962        return newobj
963
964    __rmul__ = __mul__
965
966    def __div__(self, other):
967        newobj = self.__class__(self)
968        newobj.value /= other
969        newobj._check()
970        return newobj
971
972    def __sub__(self, other):
973        newobj = self.__class__(self)
974        newobj.value -= other
975        newobj._check()
976        return newobj
977
978class Range(ParamValue):
979    type = int # default; can be overridden in subclasses
980    def __init__(self, *args, **kwargs):
981
982        def handle_kwargs(self, kwargs):
983            if 'end' in kwargs:
984                self.second = self.type(kwargs.pop('end'))
985            elif 'size' in kwargs:
986                self.second = self.first + self.type(kwargs.pop('size')) - 1
987            else:
988                raise TypeError, "Either end or size must be specified"
989
990        if len(args) == 0:
991            self.first = self.type(kwargs.pop('start'))
992            handle_kwargs(self, kwargs)
993
994        elif len(args) == 1:
995            if kwargs:
996                self.first = self.type(args[0])
997                handle_kwargs(self, kwargs)
998            elif isinstance(args[0], Range):
999                self.first = self.type(args[0].first)
1000                self.second = self.type(args[0].second)
1001            else:
1002                self.first = self.type(0)
1003                self.second = self.type(args[0]) - 1
1004
1005        elif len(args) == 2:
1006            self.first = self.type(args[0])
1007            self.second = self.type(args[1])
1008        else:
1009            raise TypeError, "Too many arguments specified"
1010
1011        if kwargs:
1012            raise TypeError, "too many keywords: %s" % kwargs.keys()
1013
1014    def __str__(self):
1015        return '%s:%s' % (self.first, self.second)
1016
1017# Metaclass for bounds-checked integer parameters.  See CheckedInt.
1018class CheckedIntType(type):
1019    def __init__(cls, name, bases, dict):
1020        super(CheckedIntType, cls).__init__(name, bases, dict)
1021
1022        # CheckedInt is an abstract base class, so we actually don't
1023        # want to do any processing on it... the rest of this code is
1024        # just for classes that derive from CheckedInt.
1025        if name == 'CheckedInt':
1026            return
1027
1028        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
1029            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
1030                panic("CheckedInt subclass %s must define either\n" \
1031                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
1032                      % name);
1033            if cls.unsigned:
1034                cls.min = 0
1035                cls.max = 2 ** cls.size - 1
1036            else:
1037                cls.min = -(2 ** (cls.size - 1))
1038                cls.max = (2 ** (cls.size - 1)) - 1
1039
1040# Abstract superclass for bounds-checked integer parameters.  This
1041# class is subclassed to generate parameter classes with specific
1042# bounds.  Initialization of the min and max bounds is done in the
1043# metaclass CheckedIntType.__init__.
1044class CheckedInt(NumericParamValue):
1045    __metaclass__ = CheckedIntType
1046
1047    def _check(self):
1048        if not self.min <= self.value <= self.max:
1049            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
1050                  (self.min, self.value, self.max)
1051
1052    def __init__(self, value):
1053        if isinstance(value, str):
1054            self.value = toInteger(value)
1055        elif isinstance(value, (int, long, float)):
1056            self.value = long(value)
1057        self._check()
1058
1059class Int(CheckedInt):      size = 32; unsigned = False
1060class Unsigned(CheckedInt): size = 32; unsigned = True
1061
1062class Int8(CheckedInt):     size =  8; unsigned = False
1063class UInt8(CheckedInt):    size =  8; unsigned = True
1064class Int16(CheckedInt):    size = 16; unsigned = False
1065class UInt16(CheckedInt):   size = 16; unsigned = True
1066class Int32(CheckedInt):    size = 32; unsigned = False
1067class UInt32(CheckedInt):   size = 32; unsigned = True
1068class Int64(CheckedInt):    size = 64; unsigned = False
1069class UInt64(CheckedInt):   size = 64; unsigned = True
1070
1071class Counter(CheckedInt):  size = 64; unsigned = True
1072class Tick(CheckedInt):     size = 64; unsigned = True
1073class TcpPort(CheckedInt):  size = 16; unsigned = True
1074class UdpPort(CheckedInt):  size = 16; unsigned = True
1075
1076class Percent(CheckedInt):  min = 0; max = 100
1077
1078class Float(ParamValue, float):
1079    pass
1080
1081class MemorySize(CheckedInt):
1082    size = 64
1083    unsigned = True
1084    def __init__(self, value):
1085        if isinstance(value, MemorySize):
1086            self.value = value.value
1087        else:
1088            self.value = toMemorySize(value)
1089        self._check()
1090
1091class MemorySize32(CheckedInt):
1092    size = 32
1093    unsigned = True
1094    def __init__(self, value):
1095        if isinstance(value, MemorySize):
1096            self.value = value.value
1097        else:
1098            self.value = toMemorySize(value)
1099        self._check()
1100
1101class Addr(CheckedInt):
1102    size = 64
1103    unsigned = True
1104    def __init__(self, value):
1105        if isinstance(value, Addr):
1106            self.value = value.value
1107        else:
1108            try:
1109                self.value = toMemorySize(value)
1110            except TypeError:
1111                self.value = long(value)
1112        self._check()
1113
1114class AddrRange(Range):
1115    type = Addr
1116
1117# String-valued parameter.  Just mixin the ParamValue class
1118# with the built-in str class.
1119class String(ParamValue,str):
1120    pass
1121
1122# Boolean parameter type.  Python doesn't let you subclass bool, since
1123# it doesn't want to let you create multiple instances of True and
1124# False.  Thus this is a little more complicated than String.
1125class Bool(ParamValue):
1126    def __init__(self, value):
1127        try:
1128            self.value = toBool(value)
1129        except TypeError:
1130            self.value = bool(value)
1131
1132    def __str__(self):
1133        return str(self.value)
1134
1135    def ini_str(self):
1136        if self.value:
1137            return 'true'
1138        return 'false'
1139
1140def IncEthernetAddr(addr, val = 1):
1141    bytes = map(lambda x: int(x, 16), addr.split(':'))
1142    bytes[5] += val
1143    for i in (5, 4, 3, 2, 1):
1144        val,rem = divmod(bytes[i], 256)
1145        bytes[i] = rem
1146        if val == 0:
1147            break
1148        bytes[i - 1] += val
1149    assert(bytes[0] <= 255)
1150    return ':'.join(map(lambda x: '%02x' % x, bytes))
1151
1152class NextEthernetAddr(object):
1153    addr = "00:90:00:00:00:01"
1154
1155    def __init__(self, inc = 1):
1156        self.value = NextEthernetAddr.addr
1157        NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc)
1158
1159class EthernetAddr(ParamValue):
1160    def __init__(self, value):
1161        if value == NextEthernetAddr:
1162            self.value = value
1163            return
1164
1165        if not isinstance(value, str):
1166            raise TypeError, "expected an ethernet address and didn't get one"
1167
1168        bytes = value.split(':')
1169        if len(bytes) != 6:
1170            raise TypeError, 'invalid ethernet address %s' % value
1171
1172        for byte in bytes:
1173            if not 0 <= int(byte) <= 256:
1174                raise TypeError, 'invalid ethernet address %s' % value
1175
1176        self.value = value
1177
1178    def unproxy(self, base):
1179        if self.value == NextEthernetAddr:
1180            self.addr = self.value().value
1181        return self
1182
1183    def __str__(self):
1184        if self.value == NextEthernetAddr:
1185            if hasattr(self, 'addr'):
1186                return self.addr
1187            else:
1188                return "NextEthernetAddr (unresolved)"
1189        else:
1190            return self.value
1191
1192# Special class for NULL pointers.  Note the special check in
1193# make_param_value() above that lets these be assigned where a
1194# SimObject is required.
1195# only one copy of a particular node
1196class NullSimObject(object):
1197    __metaclass__ = Singleton
1198
1199    def __call__(cls):
1200        return cls
1201
1202    def _instantiate(self, parent = None, path = ''):
1203        pass
1204
1205    def ini_str(self):
1206        return 'Null'
1207
1208    def unproxy(self, base):
1209        return self
1210
1211    def set_path(self, parent, name):
1212        pass
1213    def __str__(self):
1214        return 'Null'
1215
1216# The only instance you'll ever need...
1217Null = NULL = NullSimObject()
1218
1219# Enumerated types are a little more complex.  The user specifies the
1220# type as Enum(foo) where foo is either a list or dictionary of
1221# alternatives (typically strings, but not necessarily so).  (In the
1222# long run, the integer value of the parameter will be the list index
1223# or the corresponding dictionary value.  For now, since we only check
1224# that the alternative is valid and then spit it into a .ini file,
1225# there's not much point in using the dictionary.)
1226
1227# What Enum() must do is generate a new type encapsulating the
1228# provided list/dictionary so that specific values of the parameter
1229# can be instances of that type.  We define two hidden internal
1230# classes (_ListEnum and _DictEnum) to serve as base classes, then
1231# derive the new type from the appropriate base class on the fly.
1232
1233
1234# Metaclass for Enum types
1235class MetaEnum(type):
1236    def __init__(cls, name, bases, init_dict):
1237        if init_dict.has_key('map'):
1238            if not isinstance(cls.map, dict):
1239                raise TypeError, "Enum-derived class attribute 'map' " \
1240                      "must be of type dict"
1241            # build list of value strings from map
1242            cls.vals = cls.map.keys()
1243            cls.vals.sort()
1244        elif init_dict.has_key('vals'):
1245            if not isinstance(cls.vals, list):
1246                raise TypeError, "Enum-derived class attribute 'vals' " \
1247                      "must be of type list"
1248            # build string->value map from vals sequence
1249            cls.map = {}
1250            for idx,val in enumerate(cls.vals):
1251                cls.map[val] = idx
1252        else:
1253            raise TypeError, "Enum-derived class must define "\
1254                  "attribute 'map' or 'vals'"
1255
1256        super(MetaEnum, cls).__init__(name, bases, init_dict)
1257
1258    def cpp_declare(cls):
1259        s = 'enum %s {\n    ' % cls.__name__
1260        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
1261        s += '\n};\n'
1262        return s
1263
1264# Base class for enum types.
1265class Enum(ParamValue):
1266    __metaclass__ = MetaEnum
1267    vals = []
1268
1269    def __init__(self, value):
1270        if value not in self.map:
1271            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1272                  % (value, self.vals)
1273        self.value = value
1274
1275    def __str__(self):
1276        return self.value
1277
1278ticks_per_sec = None
1279
1280# how big does a rounding error need to be before we warn about it?
1281frequency_tolerance = 0.001  # 0.1%
1282
1283# convert a floting-point # of ticks to integer, and warn if rounding
1284# discards too much precision
1285def tick_check(float_ticks):
1286    if float_ticks == 0:
1287        return 0
1288    int_ticks = int(round(float_ticks))
1289    err = (float_ticks - int_ticks) / float_ticks
1290    if err > frequency_tolerance:
1291        print >> sys.stderr, "Warning: rounding error > tolerance"
1292        print >> sys.stderr, "    %f rounded to %d" % (float_ticks, int_ticks)
1293        #raise ValueError
1294    return int_ticks
1295
1296def getLatency(value):
1297    if isinstance(value, Latency) or isinstance(value, Clock):
1298        return value.value
1299    elif isinstance(value, Frequency) or isinstance(value, RootClock):
1300        return 1 / value.value
1301    elif isinstance(value, str):
1302        try:
1303            return toLatency(value)
1304        except ValueError:
1305            try:
1306                return 1 / toFrequency(value)
1307            except ValueError:
1308                pass # fall through
1309    raise ValueError, "Invalid Frequency/Latency value '%s'" % value
1310
1311
1312class Latency(NumericParamValue):
1313    def __init__(self, value):
1314        self.value = getLatency(value)
1315
1316    def __getattr__(self, attr):
1317        if attr in ('latency', 'period'):
1318            return self
1319        if attr == 'frequency':
1320            return Frequency(self)
1321        raise AttributeError, "Latency object has no attribute '%s'" % attr
1322
1323    # convert latency to ticks
1324    def ini_str(self):
1325        return str(tick_check(self.value * ticks_per_sec))
1326
1327class Frequency(NumericParamValue):
1328    def __init__(self, value):
1329        self.value = 1 / getLatency(value)
1330
1331    def __getattr__(self, attr):
1332        if attr == 'frequency':
1333            return self
1334        if attr in ('latency', 'period'):
1335            return Latency(self)
1336        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1337
1338    # convert frequency to ticks per period
1339    def ini_str(self):
1340        return self.period.ini_str()
1341
1342# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz).
1343# We can't inherit from Frequency because we don't want it to be directly
1344# assignable to a regular Frequency parameter.
1345class RootClock(ParamValue):
1346    def __init__(self, value):
1347        self.value = 1 / getLatency(value)
1348
1349    def __getattr__(self, attr):
1350        if attr == 'frequency':
1351            return Frequency(self)
1352        if attr in ('latency', 'period'):
1353            return Latency(self)
1354        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1355
1356    def ini_str(self):
1357        return str(tick_check(self.value))
1358
1359# A generic frequency and/or Latency value.  Value is stored as a latency,
1360# but to avoid ambiguity this object does not support numeric ops (* or /).
1361# An explicit conversion to a Latency or Frequency must be made first.
1362class Clock(ParamValue):
1363    def __init__(self, value):
1364        self.value = getLatency(value)
1365
1366    def __getattr__(self, attr):
1367        if attr == 'frequency':
1368            return Frequency(self)
1369        if attr in ('latency', 'period'):
1370            return Latency(self)
1371        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1372
1373    def ini_str(self):
1374        return self.period.ini_str()
1375
1376class NetworkBandwidth(float,ParamValue):
1377    def __new__(cls, value):
1378        val = toNetworkBandwidth(value) / 8.0
1379        return super(cls, NetworkBandwidth).__new__(cls, val)
1380
1381    def __str__(self):
1382        return str(self.val)
1383
1384    def ini_str(self):
1385        return '%f' % (ticks_per_sec / float(self))
1386
1387class MemoryBandwidth(float,ParamValue):
1388    def __new__(self, value):
1389        val = toMemoryBandwidth(value)
1390        return super(cls, MemoryBandwidth).__new__(cls, val)
1391
1392    def __str__(self):
1393        return str(self.val)
1394
1395    def ini_str(self):
1396        return '%f' % (ticks_per_sec / float(self))
1397
1398#
1399# "Constants"... handy aliases for various values.
1400#
1401
1402# Some memory range specifications use this as a default upper bound.
1403MaxAddr = Addr.max
1404MaxTick = Tick.max
1405AllMemory = AddrRange(0, MaxAddr)
1406
1407#####################################################################
1408
1409# __all__ defines the list of symbols that get exported when
1410# 'from config import *' is invoked.  Try to keep this reasonably
1411# short to avoid polluting other namespaces.
1412__all__ = ['SimObject', 'ParamContext', 'Param', 'VectorParam',
1413           'Parent', 'Self',
1414           'Enum', 'Bool', 'String', 'Float',
1415           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1416           'Int32', 'UInt32', 'Int64', 'UInt64',
1417           'Counter', 'Addr', 'Tick', 'Percent',
1418           'TcpPort', 'UdpPort', 'EthernetAddr',
1419           'MemorySize', 'MemorySize32',
1420           'Latency', 'Frequency', 'RootClock', 'Clock',
1421           'NetworkBandwidth', 'MemoryBandwidth',
1422           'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory',
1423           'Null', 'NULL',
1424           'NextEthernetAddr']
1425
1426