SimObject.py revision 1693
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 not _value_parent:
295            _value_parent = self.__class__
296        # clone values
297        self._values = multidict(_value_parent._values)
298        for key,val in _value_parent._values.iteritems():
299            if isSimObject(val):
300                setattr(self, key, val())
301            elif isSimObjSequence(val) and len(val):
302                setattr(self, key, [ v() for v in val ])
303        # apply attribute assignments from keyword args, if any
304        for key,val in kwargs.iteritems():
305            setattr(self, key, val)
306
307    def __call__(self, **kwargs):
308        return self.__class__(_value_parent = self, **kwargs)
309
310    def __getattr__(self, attr):
311        if self._values.has_key(attr):
312            return self._values[attr]
313
314        raise AttributeError, "object '%s' has no attribute '%s'" \
315              % (self.__class__.__name__, attr)
316
317    # Set attribute (called on foo.attr = value when foo is an
318    # instance of class cls).
319    def __setattr__(self, attr, value):
320        # normal processing for private attributes
321        if attr.startswith('_'):
322            object.__setattr__(self, attr, value)
323            return
324
325        # must be SimObject param
326        param = self._params.get(attr, None)
327        if param:
328            # It's ok: set attribute by delegating to 'object' class.
329            try:
330                value = param.convert(value)
331            except Exception, e:
332                msg = "%s\nError setting param %s.%s to %s\n" % \
333                      (e, self.__class__.__name__, attr, value)
334                e.args = (msg, )
335                raise
336        # I would love to get rid of this
337        elif isSimObject(value) or isSimObjSequence(value):
338            pass
339        else:
340            raise AttributeError, "Class %s has no parameter %s" \
341                  % (self.__class__.__name__, attr)
342
343        # clear out old child with this name, if any
344        self.clear_child(attr)
345
346        if isSimObject(value):
347            value.set_path(self, attr)
348        elif isSimObjSequence(value):
349            value = SimObjVector(value)
350            [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)]
351
352        self._values[attr] = value
353
354    # this hack allows tacking a '[0]' onto parameters that may or may
355    # not be vectors, and always getting the first element (e.g. cpus)
356    def __getitem__(self, key):
357        if key == 0:
358            return self
359        raise TypeError, "Non-zero index '%s' to SimObject" % key
360
361    # clear out children with given name, even if it's a vector
362    def clear_child(self, name):
363        if not self._children.has_key(name):
364            return
365        child = self._children[name]
366        if isinstance(child, SimObjVector):
367            for i in xrange(len(child)):
368                del self._children["s%d" % (name, i)]
369        del self._children[name]
370
371    def add_child(self, name, value):
372        self._children[name] = value
373
374    def set_path(self, parent, name):
375        if not hasattr(self, '_parent'):
376            self._parent = parent
377            self._name = name
378            parent.add_child(name, self)
379
380    def path(self):
381        if not hasattr(self, '_parent'):
382            return 'root'
383        ppath = self._parent.path()
384        if ppath == 'root':
385            return self._name
386        return ppath + "." + self._name
387
388    def __str__(self):
389        return self.path()
390
391    def ini_str(self):
392        return self.path()
393
394    def find_any(self, ptype):
395        if isinstance(self, ptype):
396            return self, True
397
398        found_obj = None
399        for child in self._children.itervalues():
400            if isinstance(child, ptype):
401                if found_obj != None and child != found_obj:
402                    raise AttributeError, \
403                          'parent.any matched more than one: %s %s' % \
404                          (obj.path, child.path)
405                found_obj = child
406        # search param space
407        for pname,pdesc in self._params.iteritems():
408            if issubclass(pdesc.ptype, ptype):
409                match_obj = self._values[pname]
410                if found_obj != None and found_obj != match_obj:
411                    raise AttributeError, \
412                          'parent.any matched more than one: %s' % obj.path
413                found_obj = match_obj
414        return found_obj, found_obj != None
415
416    def print_ini(self):
417        print '[' + self.path() + ']'	# .ini section header
418
419        if hasattr(self, 'type') and not isinstance(self, ParamContext):
420            print 'type =', self.type
421
422        child_names = self._children.keys()
423        child_names.sort()
424        np_child_names = [c for c in child_names \
425                          if not isinstance(self._children[c], ParamContext)]
426        if len(np_child_names):
427            print 'children =', ' '.join(np_child_names)
428
429        param_names = self._params.keys()
430        param_names.sort()
431        for param in param_names:
432            value = self._values.get(param, None)
433            if value != None:
434                if isproxy(value):
435                    try:
436                        value = value.unproxy(self)
437                    except:
438                        print >> sys.stderr, \
439                              "Error in unproxying param '%s' of %s" % \
440                              (param, self.path())
441                        raise
442                    setattr(self, param, value)
443                print param, '=', self._values[param].ini_str()
444
445        print	# blank line between objects
446
447        for child in child_names:
448            self._children[child].print_ini()
449
450    # generate output file for 'dot' to display as a pretty graph.
451    # this code is currently broken.
452    def outputDot(self, dot):
453        label = "{%s|" % self.path
454        if isSimObject(self.realtype):
455            label +=  '%s|' % self.type
456
457        if self.children:
458            # instantiate children in same order they were added for
459            # backward compatibility (else we can end up with cpu1
460            # before cpu0).
461            for c in self.children:
462                dot.add_edge(pydot.Edge(self.path,c.path, style="bold"))
463
464        simobjs = []
465        for param in self.params:
466            try:
467                if param.value is None:
468                    raise AttributeError, 'Parameter with no value'
469
470                value = param.value
471                string = param.string(value)
472            except Exception, e:
473                msg = 'exception in %s:%s\n%s' % (self.name, param.name, e)
474                e.args = (msg, )
475                raise
476
477            if isSimObject(param.ptype) and string != "Null":
478                simobjs.append(string)
479            else:
480                label += '%s = %s\\n' % (param.name, string)
481
482        for so in simobjs:
483            label += "|<%s> %s" % (so, so)
484            dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so,
485                                    tailport="w"))
486        label += '}'
487        dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label))
488
489        # recursively dump out children
490        for c in self.children:
491            c.outputDot(dot)
492
493class ParamContext(SimObject):
494    pass
495
496#####################################################################
497#
498# Proxy object support.
499#
500#####################################################################
501
502class BaseProxy(object):
503    def __init__(self, search_self, search_up):
504        self._search_self = search_self
505        self._search_up = search_up
506        self._multiplier = None
507
508    def __setattr__(self, attr, value):
509        if not attr.startswith('_'):
510            raise AttributeError, 'cannot set attribute on proxy object'
511        super(BaseProxy, self).__setattr__(attr, value)
512
513    # support multiplying proxies by constants
514    def __mul__(self, other):
515        if not isinstance(other, (int, float)):
516            raise TypeError, "Proxy multiplier must be integer"
517        if self._multiplier == None:
518            self._multiplier = other
519        else:
520            # support chained multipliers
521            self._multiplier *= other
522        return self
523
524    __rmul__ = __mul__
525
526    def _mulcheck(self, result):
527        if self._multiplier == None:
528            return result
529        return result * self._multiplier
530
531    def unproxy(self, base):
532        obj = base
533        done = False
534
535        if self._search_self:
536            result, done = self.find(obj)
537
538        if self._search_up:
539            while not done:
540                try: obj = obj._parent
541                except: break
542
543                result, done = self.find(obj)
544
545        if not done:
546            raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \
547                  (self.path(), base.path())
548
549        if isinstance(result, BaseProxy):
550            if result == self:
551                raise RuntimeError, "Cycle in unproxy"
552            result = result.unproxy(obj)
553
554        return self._mulcheck(result)
555
556    def getindex(obj, index):
557        if index == None:
558            return obj
559        try:
560            obj = obj[index]
561        except TypeError:
562            if index != 0:
563                raise
564            # if index is 0 and item is not subscriptable, just
565            # use item itself (so cpu[0] works on uniprocessors)
566        return obj
567    getindex = staticmethod(getindex)
568
569    def set_param_desc(self, pdesc):
570        self._pdesc = pdesc
571
572class AttrProxy(BaseProxy):
573    def __init__(self, search_self, search_up, attr):
574        super(AttrProxy, self).__init__(search_self, search_up)
575        self._attr = attr
576        self._modifiers = []
577
578    def __getattr__(self, attr):
579        # python uses __bases__ internally for inheritance
580        if attr.startswith('_'):
581            return super(AttrProxy, self).__getattr__(self, attr)
582        if hasattr(self, '_pdesc'):
583            raise AttributeError, "Attribute reference on bound proxy"
584        self._modifiers.append(attr)
585        return self
586
587    # support indexing on proxies (e.g., Self.cpu[0])
588    def __getitem__(self, key):
589        if not isinstance(key, int):
590            raise TypeError, "Proxy object requires integer index"
591        self._modifiers.append(key)
592        return self
593
594    def find(self, obj):
595        try:
596            val = getattr(obj, self._attr)
597        except:
598            return None, False
599        while isproxy(val):
600            val = val.unproxy(obj)
601        for m in self._modifiers:
602            if isinstance(m, str):
603                val = getattr(val, m)
604            elif isinstance(m, int):
605                val = val[m]
606            else:
607                assert("Item must be string or integer")
608            while isproxy(val):
609                val = val.unproxy(obj)
610        return val, True
611
612    def path(self):
613        p = self._attr
614        for m in self._modifiers:
615            if isinstance(m, str):
616                p += '.%s' % m
617            elif isinstance(m, int):
618                p += '[%d]' % m
619            else:
620                assert("Item must be string or integer")
621        return p
622
623class AnyProxy(BaseProxy):
624    def find(self, obj):
625        return obj.find_any(self._pdesc.ptype)
626
627    def path(self):
628        return 'any'
629
630def isproxy(obj):
631    if isinstance(obj, BaseProxy):
632        return True
633    elif isinstance(obj, (list, tuple)):
634        for v in obj:
635            if isproxy(v):
636                return True
637    return False
638
639class ProxyFactory(object):
640    def __init__(self, search_self, search_up):
641        self.search_self = search_self
642        self.search_up = search_up
643
644    def __getattr__(self, attr):
645        if attr == 'any':
646            return AnyProxy(self.search_self, self.search_up)
647        else:
648            return AttrProxy(self.search_self, self.search_up, attr)
649
650# global objects for handling proxies
651Parent = ProxyFactory(search_self = False, search_up = True)
652Self = ProxyFactory(search_self = True, search_up = False)
653
654#####################################################################
655#
656# Parameter description classes
657#
658# The _params dictionary in each class maps parameter names to
659# either a Param or a VectorParam object.  These objects contain the
660# parameter description string, the parameter type, and the default
661# value (loaded from the PARAM section of the .odesc files).  The
662# _convert() method on these objects is used to force whatever value
663# is assigned to the parameter to the appropriate type.
664#
665# Note that the default values are loaded into the class's attribute
666# space when the parameter dictionary is initialized (in
667# MetaConfigNode._setparams()); after that point they aren't used.
668#
669#####################################################################
670
671# Dummy base class to identify types that are legitimate for SimObject
672# parameters.
673class ParamValue(object):
674
675    # default for printing to .ini file is regular string conversion.
676    # will be overridden in some cases
677    def ini_str(self):
678        return str(self)
679
680    # allows us to blithely call unproxy() on things without checking
681    # if they're really proxies or not
682    def unproxy(self, base):
683        return self
684
685# Regular parameter description.
686class ParamDesc(object):
687    def __init__(self, ptype_str, ptype, *args, **kwargs):
688        self.ptype_str = ptype_str
689        # remember ptype only if it is provided
690        if ptype != None:
691            self.ptype = ptype
692
693        if args:
694            if len(args) == 1:
695                self.desc = args[0]
696            elif len(args) == 2:
697                self.default = args[0]
698                self.desc = args[1]
699            else:
700                raise TypeError, 'too many arguments'
701
702        if kwargs.has_key('desc'):
703            assert(not hasattr(self, 'desc'))
704            self.desc = kwargs['desc']
705            del kwargs['desc']
706
707        if kwargs.has_key('default'):
708            assert(not hasattr(self, 'default'))
709            self.default = kwargs['default']
710            del kwargs['default']
711
712        if kwargs:
713            raise TypeError, 'extra unknown kwargs %s' % kwargs
714
715        if not hasattr(self, 'desc'):
716            raise TypeError, 'desc attribute missing'
717
718    def __getattr__(self, attr):
719        if attr == 'ptype':
720            try:
721                ptype = eval(self.ptype_str, m5.__dict__)
722                if not isinstance(ptype, type):
723                    panic("Param qualifier is not a type: %s" % self.ptype)
724                self.ptype = ptype
725                return ptype
726            except NameError:
727                pass
728        raise AttributeError, "'%s' object has no attribute '%s'" % \
729              (type(self).__name__, attr)
730
731    def convert(self, value):
732        if isinstance(value, BaseProxy):
733            value.set_param_desc(self)
734            return value
735        if not hasattr(self, 'ptype') and isNullPointer(value):
736            # deferred evaluation of SimObject; continue to defer if
737            # we're just assigning a null pointer
738            return value
739        if isinstance(value, self.ptype):
740            return value
741        if isNullPointer(value) and issubclass(self.ptype, SimObject):
742            return value
743        return self.ptype(value)
744
745# Vector-valued parameter description.  Just like ParamDesc, except
746# that the value is a vector (list) of the specified type instead of a
747# single value.
748
749class VectorParamValue(list):
750    def ini_str(self):
751        return ' '.join([str(v) for v in self])
752
753    def unproxy(self, base):
754        return [v.unproxy(base) for v in self]
755
756class SimObjVector(VectorParamValue):
757    def print_ini(self):
758        for v in self:
759            v.print_ini()
760
761class VectorParamDesc(ParamDesc):
762    # Convert assigned value to appropriate type.  If the RHS is not a
763    # list or tuple, it generates a single-element list.
764    def convert(self, value):
765        if isinstance(value, (list, tuple)):
766            # list: coerce each element into new list
767            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
768            if isSimObjSequence(tmp_list):
769                return SimObjVector(tmp_list)
770            else:
771                return VectorParamValue(tmp_list)
772        else:
773            # singleton: leave it be (could coerce to a single-element
774            # list here, but for some historical reason we don't...
775            return ParamDesc.convert(self, value)
776
777
778class ParamFactory(object):
779    def __init__(self, param_desc_class, ptype_str = None):
780        self.param_desc_class = param_desc_class
781        self.ptype_str = ptype_str
782
783    def __getattr__(self, attr):
784        if self.ptype_str:
785            attr = self.ptype_str + '.' + attr
786        return ParamFactory(self.param_desc_class, attr)
787
788    # E.g., Param.Int(5, "number of widgets")
789    def __call__(self, *args, **kwargs):
790        caller_frame = inspect.stack()[1][0]
791        ptype = None
792        try:
793            ptype = eval(self.ptype_str,
794                         caller_frame.f_globals, caller_frame.f_locals)
795            if not isinstance(ptype, type):
796                raise TypeError, \
797                      "Param qualifier is not a type: %s" % ptype
798        except NameError:
799            # if name isn't defined yet, assume it's a SimObject, and
800            # try to resolve it later
801            pass
802        return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs)
803
804Param = ParamFactory(ParamDesc)
805VectorParam = ParamFactory(VectorParamDesc)
806
807#####################################################################
808#
809# Parameter Types
810#
811# Though native Python types could be used to specify parameter types
812# (the 'ptype' field of the Param and VectorParam classes), it's more
813# flexible to define our own set of types.  This gives us more control
814# over how Python expressions are converted to values (via the
815# __init__() constructor) and how these values are printed out (via
816# the __str__() conversion method).  Eventually we'll need these types
817# to correspond to distinct C++ types as well.
818#
819#####################################################################
820
821class Range(ParamValue):
822    type = int # default; can be overridden in subclasses
823    def __init__(self, *args, **kwargs):
824
825        def handle_kwargs(self, kwargs):
826            if 'end' in kwargs:
827                self.second = self.type(kwargs.pop('end'))
828            elif 'size' in kwargs:
829                self.second = self.first + self.type(kwargs.pop('size')) - 1
830            else:
831                raise TypeError, "Either end or size must be specified"
832
833        if len(args) == 0:
834            self.first = self.type(kwargs.pop('start'))
835            handle_kwargs(self, kwargs)
836
837        elif len(args) == 1:
838            if kwargs:
839                self.first = self.type(args[0])
840                handle_kwargs(self, kwargs)
841            elif isinstance(args[0], Range):
842                self.first = self.type(args[0].first)
843                self.second = self.type(args[0].second)
844            else:
845                self.first = self.type(0)
846                self.second = self.type(args[0]) - 1
847
848        elif len(args) == 2:
849            self.first = self.type(args[0])
850            self.second = self.type(args[1])
851        else:
852            raise TypeError, "Too many arguments specified"
853
854        if kwargs:
855            raise TypeError, "too many keywords: %s" % kwargs.keys()
856
857    def __str__(self):
858        return '%s:%s' % (self.first, self.second)
859
860# Metaclass for bounds-checked integer parameters.  See CheckedInt.
861class CheckedIntType(type):
862    def __init__(cls, name, bases, dict):
863        super(CheckedIntType, cls).__init__(name, bases, dict)
864
865        # CheckedInt is an abstract base class, so we actually don't
866        # want to do any processing on it... the rest of this code is
867        # just for classes that derive from CheckedInt.
868        if name == 'CheckedInt':
869            return
870
871        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
872            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
873                panic("CheckedInt subclass %s must define either\n" \
874                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
875                      % name);
876            if cls.unsigned:
877                cls.min = 0
878                cls.max = 2 ** cls.size - 1
879            else:
880                cls.min = -(2 ** (cls.size - 1))
881                cls.max = (2 ** (cls.size - 1)) - 1
882
883# Abstract superclass for bounds-checked integer parameters.  This
884# class is subclassed to generate parameter classes with specific
885# bounds.  Initialization of the min and max bounds is done in the
886# metaclass CheckedIntType.__init__.
887class CheckedInt(long,ParamValue):
888    __metaclass__ = CheckedIntType
889
890    def __new__(cls, value):
891        if isinstance(value, str):
892            value = toInteger(value)
893
894        self = long.__new__(cls, value)
895
896        if not cls.min <= self <= cls.max:
897            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
898                  (cls.min, self, cls.max)
899        return self
900
901class Int(CheckedInt):      size = 32; unsigned = False
902class Unsigned(CheckedInt): size = 32; unsigned = True
903
904class Int8(CheckedInt):     size =  8; unsigned = False
905class UInt8(CheckedInt):    size =  8; unsigned = True
906class Int16(CheckedInt):    size = 16; unsigned = False
907class UInt16(CheckedInt):   size = 16; unsigned = True
908class Int32(CheckedInt):    size = 32; unsigned = False
909class UInt32(CheckedInt):   size = 32; unsigned = True
910class Int64(CheckedInt):    size = 64; unsigned = False
911class UInt64(CheckedInt):   size = 64; unsigned = True
912
913class Counter(CheckedInt):  size = 64; unsigned = True
914class Tick(CheckedInt):     size = 64; unsigned = True
915class TcpPort(CheckedInt):  size = 16; unsigned = True
916class UdpPort(CheckedInt):  size = 16; unsigned = True
917
918class Percent(CheckedInt):  min = 0; max = 100
919
920class MemorySize(CheckedInt):
921    size = 64
922    unsigned = True
923    def __new__(cls, value):
924        return super(MemorySize, cls).__new__(cls, toMemorySize(value))
925
926
927class Addr(CheckedInt):
928    size = 64
929    unsigned = True
930    def __new__(cls, value):
931        try:
932            value = long(toMemorySize(value))
933        except TypeError:
934            value = long(value)
935        return super(Addr, cls).__new__(cls, value)
936
937class AddrRange(Range):
938    type = Addr
939
940# String-valued parameter.  Just mixin the ParamValue class
941# with the built-in str class.
942class String(ParamValue,str):
943    pass
944
945# Boolean parameter type.  Python doesn't let you subclass bool, since
946# it doesn't want to let you create multiple instances of True and
947# False.  Thus this is a little more complicated than String.
948class Bool(ParamValue):
949    def __init__(self, value):
950        try:
951            self.value = toBool(value)
952        except TypeError:
953            self.value = bool(value)
954
955    def __str__(self):
956        return str(self.value)
957
958    def ini_str(self):
959        if self.value:
960            return 'true'
961        return 'false'
962
963def IncEthernetAddr(addr, val = 1):
964    bytes = map(lambda x: int(x, 16), addr.split(':'))
965    bytes[5] += val
966    for i in (5, 4, 3, 2, 1):
967        val,rem = divmod(bytes[i], 256)
968        bytes[i] = rem
969        if val == 0:
970            break
971        bytes[i - 1] += val
972    assert(bytes[0] <= 255)
973    return ':'.join(map(lambda x: '%02x' % x, bytes))
974
975class NextEthernetAddr(object):
976    __metaclass__ = Singleton
977    addr = "00:90:00:00:00:01"
978
979    def __init__(self, inc = 1):
980        self.value = self.addr
981        self.addr = IncEthernetAddr(self.addr, inc)
982
983class EthernetAddr(ParamValue):
984    def __init__(self, value):
985        if value == NextEthernetAddr:
986            self.value = value
987            return
988
989        if not isinstance(value, str):
990            raise TypeError, "expected an ethernet address and didn't get one"
991
992        bytes = value.split(':')
993        if len(bytes) != 6:
994            raise TypeError, 'invalid ethernet address %s' % value
995
996        for byte in bytes:
997            if not 0 <= int(byte) <= 256:
998                raise TypeError, 'invalid ethernet address %s' % value
999
1000        self.value = value
1001
1002    def __str__(self):
1003        if self.value == NextEthernetAddr:
1004            self.value = self.value().value
1005        return self.value
1006
1007# Special class for NULL pointers.  Note the special check in
1008# make_param_value() above that lets these be assigned where a
1009# SimObject is required.
1010# only one copy of a particular node
1011class NullSimObject(object):
1012    __metaclass__ = Singleton
1013
1014    def __call__(cls):
1015        return cls
1016
1017    def _instantiate(self, parent = None, path = ''):
1018        pass
1019
1020    def ini_str(self):
1021        return 'Null'
1022
1023# The only instance you'll ever need...
1024Null = NULL = NullSimObject()
1025
1026# Enumerated types are a little more complex.  The user specifies the
1027# type as Enum(foo) where foo is either a list or dictionary of
1028# alternatives (typically strings, but not necessarily so).  (In the
1029# long run, the integer value of the parameter will be the list index
1030# or the corresponding dictionary value.  For now, since we only check
1031# that the alternative is valid and then spit it into a .ini file,
1032# there's not much point in using the dictionary.)
1033
1034# What Enum() must do is generate a new type encapsulating the
1035# provided list/dictionary so that specific values of the parameter
1036# can be instances of that type.  We define two hidden internal
1037# classes (_ListEnum and _DictEnum) to serve as base classes, then
1038# derive the new type from the appropriate base class on the fly.
1039
1040
1041# Metaclass for Enum types
1042class MetaEnum(type):
1043    def __init__(cls, name, bases, init_dict):
1044        if init_dict.has_key('map'):
1045            if not isinstance(cls.map, dict):
1046                raise TypeError, "Enum-derived class attribute 'map' " \
1047                      "must be of type dict"
1048            # build list of value strings from map
1049            cls.vals = cls.map.keys()
1050            cls.vals.sort()
1051        elif init_dict.has_key('vals'):
1052            if not isinstance(cls.vals, list):
1053                raise TypeError, "Enum-derived class attribute 'vals' " \
1054                      "must be of type list"
1055            # build string->value map from vals sequence
1056            cls.map = {}
1057            for idx,val in enumerate(cls.vals):
1058                cls.map[val] = idx
1059        else:
1060            raise TypeError, "Enum-derived class must define "\
1061                  "attribute 'map' or 'vals'"
1062
1063        super(MetaEnum, cls).__init__(name, bases, init_dict)
1064
1065    def cpp_declare(cls):
1066        s = 'enum %s {\n    ' % cls.__name__
1067        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
1068        s += '\n};\n'
1069        return s
1070
1071# Base class for enum types.
1072class Enum(ParamValue):
1073    __metaclass__ = MetaEnum
1074    vals = []
1075
1076    def __init__(self, value):
1077        if value not in self.map:
1078            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1079                  % (value, self.vals)
1080        self.value = value
1081
1082    def __str__(self):
1083        return self.value
1084
1085ticks_per_sec = None
1086
1087# how big does a rounding error need to be before we warn about it?
1088frequency_tolerance = 0.001  # 0.1%
1089
1090# convert a floting-point # of ticks to integer, and warn if rounding
1091# discards too much precision
1092def tick_check(float_ticks):
1093    if float_ticks == 0:
1094        return 0
1095    int_ticks = int(round(float_ticks))
1096    err = (float_ticks - int_ticks) / float_ticks
1097    if err > frequency_tolerance:
1098        print >> sys.stderr, "Warning: rounding error > tolerance"
1099    return int_ticks
1100
1101# superclass for "numeric" parameter values, to emulate math
1102# operations in a type-safe way.  e.g., a Latency times an int returns
1103# a new Latency object.
1104class NumericParamValue(ParamValue):
1105    def __mul__(self, other):
1106        newobj = self.__class__(self)
1107        newobj.value *= other
1108        return newobj
1109
1110    __rmul__ = __mul__
1111
1112class Latency(NumericParamValue):
1113    def __init__(self, value):
1114        if isinstance(value, Latency):
1115            self.value = value.value
1116        elif isinstance(value, Frequency):
1117            self.value = 1 / value.value
1118        elif isinstance(value, str):
1119            try:
1120                self.value = toLatency(value)
1121            except ValueError:
1122                try:
1123                    freq = toFrequency(value)
1124                except ValueError:
1125                    raise ValueError, "Latency value '%s' is neither " \
1126                          "frequency nor period" % value
1127                self.value = 1 / freq
1128        elif value == 0:
1129            # the one unitless value that's OK...
1130            self.value = value
1131        else:
1132            raise ValueError, "Invalid Latency value '%s'" % value
1133
1134    def __getattr__(self, attr):
1135        if attr in ('latency', 'period'):
1136            return self
1137        if attr == 'frequency':
1138            return Frequency(self)
1139        raise AttributeError, "Latency object has no attribute '%s'" % attr
1140
1141    def __str__(self):
1142        return str(self.value)
1143
1144    # convert latency to ticks
1145    def ini_str(self):
1146        return str(tick_check(self.value * ticks_per_sec))
1147
1148class Frequency(NumericParamValue):
1149    def __init__(self, value):
1150        if isinstance(value, Frequency):
1151            self.value = value.value
1152        elif isinstance(value, Latency):
1153            self.value = 1 / value.value
1154        elif isinstance(value, str):
1155            try:
1156                self.value = toFrequency(value)
1157            except ValueError:
1158                try:
1159                    freq = toLatency(value)
1160                except ValueError:
1161                    raise ValueError, "Frequency value '%s' is neither " \
1162                          "frequency nor period" % value
1163                self.value = 1 / freq
1164        else:
1165            raise ValueError, "Invalid Frequency value '%s'" % value
1166
1167    def __getattr__(self, attr):
1168        if attr == 'frequency':
1169            return self
1170        if attr in ('latency', 'period'):
1171            return Latency(self)
1172        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1173
1174    def __str__(self):
1175        return str(self.value)
1176
1177    def __float__(self):
1178        return float(self.value)
1179
1180    # convert frequency to ticks per period
1181    def ini_str(self):
1182        return self.period.ini_str()
1183
1184# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz)
1185class RootFrequency(Frequency):
1186    def ini_str(self):
1187        return str(tick_check(self.value))
1188
1189class NetworkBandwidth(float,ParamValue):
1190    def __new__(cls, value):
1191        val = toNetworkBandwidth(value) / 8.0
1192        return super(cls, NetworkBandwidth).__new__(cls, val)
1193
1194    def __str__(self):
1195        return str(self.val)
1196
1197    def ini_str(self):
1198        return '%f' % (ticks_per_sec / self.val)
1199
1200class MemoryBandwidth(float,ParamValue):
1201    def __new__(self, value):
1202        val = toMemoryBandwidth(value)
1203        return super(cls, MemoryBandwidth).__new__(cls, val)
1204
1205    def __str__(self):
1206        return str(self.val)
1207
1208    def ini_str(self):
1209        return '%f' % (ticks_per_sec / self.val)
1210
1211#
1212# "Constants"... handy aliases for various values.
1213#
1214
1215# Some memory range specifications use this as a default upper bound.
1216MaxAddr = Addr.max
1217MaxTick = Tick.max
1218AllMemory = AddrRange(0, MaxAddr)
1219
1220#####################################################################
1221
1222# The final hook to generate .ini files.  Called from configuration
1223# script once config is built.
1224def instantiate(root):
1225    global ticks_per_sec
1226    ticks_per_sec = float(root.frequency)
1227    root.print_ini()
1228    noDot = True # temporary until we fix dot
1229    if not noDot:
1230       dot = pydot.Dot()
1231       instance.outputDot(dot)
1232       dot.orientation = "portrait"
1233       dot.size = "8.5,11"
1234       dot.ranksep="equally"
1235       dot.rank="samerank"
1236       dot.write("config.dot")
1237       dot.write_ps("config.ps")
1238
1239# __all__ defines the list of symbols that get exported when
1240# 'from config import *' is invoked.  Try to keep this reasonably
1241# short to avoid polluting other namespaces.
1242__all__ = ['SimObject', 'ParamContext', 'Param', 'VectorParam',
1243           'Parent', 'Self',
1244           'Enum', 'Bool', 'String',
1245           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1246           'Int32', 'UInt32', 'Int64', 'UInt64',
1247           'Counter', 'Addr', 'Tick', 'Percent',
1248           'TcpPort', 'UdpPort', 'EthernetAddr',
1249           'MemorySize', 'Latency', 'Frequency', 'RootFrequency',
1250           'NetworkBandwidth', 'MemoryBandwidth',
1251           'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory',
1252           'Null', 'NULL',
1253           'NextEthernetAddr', 'instantiate']
1254
1255