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