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