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