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