SimObject.py revision 3100
1# Copyright (c) 2004-2006 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
30import os, re, sys, types, inspect, copy
31
32import m5
33from m5 import panic, cc_main
34from convert import *
35from multidict import multidict
36
37noDot = False
38try:
39    import pydot
40except:
41    noDot = True
42
43class Singleton(type):
44    def __call__(cls, *args, **kwargs):
45        if hasattr(cls, '_instance'):
46            return cls._instance
47
48        cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
49        return cls._instance
50
51#####################################################################
52#
53# M5 Python Configuration Utility
54#
55# The basic idea is to write simple Python programs that build Python
56# objects corresponding to M5 SimObjects for the desired simulation
57# configuration.  For now, the Python emits a .ini file that can be
58# parsed by M5.  In the future, some tighter integration between M5
59# and the Python interpreter may allow bypassing the .ini file.
60#
61# Each SimObject class in M5 is represented by a Python class with the
62# same name.  The Python inheritance tree mirrors the M5 C++ tree
63# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
64# SimObjects inherit from a single SimObject base class).  To specify
65# an instance of an M5 SimObject in a configuration, the user simply
66# instantiates the corresponding Python object.  The parameters for
67# that SimObject are given by assigning to attributes of the Python
68# object, either using keyword assignment in the constructor or in
69# separate assignment statements.  For example:
70#
71# cache = BaseCache(size='64KB')
72# cache.hit_latency = 3
73# cache.assoc = 8
74#
75# The magic lies in the mapping of the Python attributes for SimObject
76# classes to the actual SimObject parameter specifications.  This
77# allows parameter validity checking in the Python code.  Continuing
78# the example above, the statements "cache.blurfl=3" or
79# "cache.assoc='hello'" would both result in runtime errors in Python,
80# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
81# parameter requires an integer, respectively.  This magic is done
82# primarily by overriding the special __setattr__ method that controls
83# assignment to object attributes.
84#
85# Once a set of Python objects have been instantiated in a hierarchy,
86# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
87# will generate a .ini file.
88#
89#####################################################################
90
91# dict to look up SimObjects based on path
92instanceDict = {}
93
94#############################
95#
96# Utility methods
97#
98#############################
99
100def isSimObject(value):
101    return isinstance(value, SimObject)
102
103def isSimObjectSequence(value):
104    if not isinstance(value, (list, tuple)) or len(value) == 0:
105        return False
106
107    for val in value:
108        if not isNullPointer(val) and not isSimObject(val):
109            return False
110
111    return True
112
113def isSimObjectOrSequence(value):
114    return isSimObject(value) or isSimObjectSequence(value)
115
116def isNullPointer(value):
117    return isinstance(value, NullSimObject)
118
119# Apply method to object.
120# applyMethod(obj, 'meth', <args>) is equivalent to obj.meth(<args>)
121def applyMethod(obj, meth, *args, **kwargs):
122    return getattr(obj, meth)(*args, **kwargs)
123
124# If the first argument is an (non-sequence) object, apply the named
125# method with the given arguments.  If the first argument is a
126# sequence, apply the method to each element of the sequence (a la
127# 'map').
128def applyOrMap(objOrSeq, meth, *args, **kwargs):
129    if not isinstance(objOrSeq, (list, tuple)):
130        return applyMethod(objOrSeq, meth, *args, **kwargs)
131    else:
132        return [applyMethod(o, meth, *args, **kwargs) for o in objOrSeq]
133
134
135# The metaclass for SimObject.  This class controls how new classes
136# that derive from SimObject are instantiated, and provides inherited
137# class behavior (just like a class controls how instances of that
138# class are instantiated, and provides inherited instance behavior).
139class MetaSimObject(type):
140    # Attributes that can be set only at initialization time
141    init_keywords = { 'abstract' : types.BooleanType,
142                      'type' : types.StringType }
143    # Attributes that can be set any time
144    keywords = { 'check' : types.FunctionType,
145                 'cxx_type' : types.StringType,
146                 'cxx_predecls' : types.ListType,
147                 'swig_predecls' : types.ListType }
148
149    # __new__ is called before __init__, and is where the statements
150    # in the body of the class definition get loaded into the class's
151    # __dict__.  We intercept this to filter out parameter & port assignments
152    # and only allow "private" attributes to be passed to the base
153    # __new__ (starting with underscore).
154    def __new__(mcls, name, bases, dict):
155        # Copy "private" attributes, functions, and classes to the
156        # official dict.  Everything else goes in _init_dict to be
157        # filtered in __init__.
158        cls_dict = {}
159        value_dict = {}
160        for key,val in dict.items():
161            if key.startswith('_') or isinstance(val, (types.FunctionType,
162                                                       types.TypeType)):
163                cls_dict[key] = val
164            else:
165                # must be a param/port setting
166                value_dict[key] = val
167        cls_dict['_value_dict'] = value_dict
168        return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
169
170    # subclass initialization
171    def __init__(cls, name, bases, dict):
172        # calls type.__init__()... I think that's a no-op, but leave
173        # it here just in case it's not.
174        super(MetaSimObject, cls).__init__(name, bases, dict)
175
176        # initialize required attributes
177
178        # class-only attributes
179        cls._params = multidict() # param descriptions
180        cls._ports = multidict()  # port descriptions
181
182        # class or instance attributes
183        cls._values = multidict()   # param values
184        cls._port_map = multidict() # port bindings
185        cls._instantiated = False # really instantiated, cloned, or subclassed
186
187        # We don't support multiple inheritance.  If you want to, you
188        # must fix multidict to deal with it properly.
189        if len(bases) > 1:
190            raise TypeError, "SimObjects do not support multiple inheritance"
191
192        base = bases[0]
193
194        # Set up general inheritance via multidicts.  A subclass will
195        # inherit all its settings from the base class.  The only time
196        # the following is not true is when we define the SimObject
197        # class itself (in which case the multidicts have no parent).
198        if isinstance(base, MetaSimObject):
199            cls._params.parent = base._params
200            cls._ports.parent = base._ports
201            cls._values.parent = base._values
202            cls._port_map.parent = base._port_map
203            # mark base as having been subclassed
204            base._instantiated = True
205
206        # Now process the _value_dict items.  They could be defining
207        # new (or overriding existing) parameters or ports, setting
208        # class keywords (e.g., 'abstract'), or setting parameter
209        # values or port bindings.  The first 3 can only be set when
210        # the class is defined, so we handle them here.  The others
211        # can be set later too, so just emulate that by calling
212        # setattr().
213        for key,val in cls._value_dict.items():
214            # param descriptions
215            if isinstance(val, ParamDesc):
216                cls._new_param(key, val)
217
218            # port objects
219            elif isinstance(val, Port):
220                cls._ports[key] = val
221
222            # init-time-only keywords
223            elif cls.init_keywords.has_key(key):
224                cls._set_keyword(key, val, cls.init_keywords[key])
225
226            # default: use normal path (ends up in __setattr__)
227            else:
228                setattr(cls, key, val)
229
230        cls.cxx_type = cls.type + '*'
231        # A forward class declaration is sufficient since we are just
232        # declaring a pointer.
233        cls.cxx_predecls = ['class %s;' % cls.type]
234        cls.swig_predecls = cls.cxx_predecls
235
236    def _set_keyword(cls, keyword, val, kwtype):
237        if not isinstance(val, kwtype):
238            raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
239                  (keyword, type(val), kwtype)
240        if isinstance(val, types.FunctionType):
241            val = classmethod(val)
242        type.__setattr__(cls, keyword, val)
243
244    def _new_param(cls, name, pdesc):
245        # each param desc should be uniquely assigned to one variable
246        assert(not hasattr(pdesc, 'name'))
247        pdesc.name = name
248        cls._params[name] = pdesc
249        if hasattr(pdesc, 'default'):
250            setattr(cls, name, pdesc.default)
251
252    # Set attribute (called on foo.attr = value when foo is an
253    # instance of class cls).
254    def __setattr__(cls, attr, value):
255        # normal processing for private attributes
256        if attr.startswith('_'):
257            type.__setattr__(cls, attr, value)
258            return
259
260        if cls.keywords.has_key(attr):
261            cls._set_keyword(attr, value, cls.keywords[attr])
262            return
263
264        if cls._ports.has_key(attr):
265            self._ports[attr].connect(self, attr, value)
266            return
267
268        if isSimObjectOrSequence(value) and cls._instantiated:
269            raise RuntimeError, \
270                  "cannot set SimObject parameter '%s' after\n" \
271                  "    class %s has been instantiated or subclassed" \
272                  % (attr, cls.__name__)
273
274        # check for param
275        param = cls._params.get(attr, None)
276        if param:
277            try:
278                cls._values[attr] = param.convert(value)
279            except Exception, e:
280                msg = "%s\nError setting param %s.%s to %s\n" % \
281                      (e, cls.__name__, attr, value)
282                e.args = (msg, )
283                raise
284        elif isSimObjectOrSequence(value):
285            # if RHS is a SimObject, it's an implicit child assignment
286            cls._values[attr] = value
287        else:
288            raise AttributeError, \
289                  "Class %s has no parameter \'%s\'" % (cls.__name__, attr)
290
291    def __getattr__(cls, attr):
292        if cls._values.has_key(attr):
293            return cls._values[attr]
294
295        raise AttributeError, \
296              "object '%s' has no attribute '%s'" % (cls.__name__, attr)
297
298    def __str__(cls):
299        return cls.__name__
300
301    def cxx_decl(cls):
302        code = "#ifndef __PARAMS__%s\n#define __PARAMS__%s\n\n" % (cls, cls)
303
304        if str(cls) != 'SimObject':
305            base = cls.__bases__[0].type
306        else:
307            base = None
308
309        # The 'dict' attribute restricts us to the params declared in
310        # the object itself, not including inherited params (which
311        # will also be inherited from the base class's param struct
312        # here).
313        params = cls._params.dict.values()
314        try:
315            ptypes = [p.ptype for p in params]
316        except:
317            print cls, p, p.ptype_str
318            print params
319            raise
320
321        # get a list of lists of predeclaration lines
322        predecls = [p.cxx_predecls() for p in params]
323        # flatten
324        predecls = reduce(lambda x,y:x+y, predecls, [])
325        # remove redundant lines
326        predecls2 = []
327        for pd in predecls:
328            if pd not in predecls2:
329                predecls2.append(pd)
330        predecls2.sort()
331        code += "\n".join(predecls2)
332        code += "\n\n";
333
334        if base:
335            code += '#include "params/%s.hh"\n\n' % base
336
337        # Generate declarations for locally defined enumerations.
338        enum_ptypes = [t for t in ptypes if issubclass(t, Enum)]
339        if enum_ptypes:
340            code += "\n".join([t.cxx_decl() for t in enum_ptypes])
341            code += "\n\n"
342
343        # now generate the actual param struct
344        code += "struct %sParams" % cls
345        if base:
346            code += " : public %sParams" % base
347        code += " {\n"
348        decls = [p.cxx_decl() for p in params]
349        decls.sort()
350        code += "".join(["    %s\n" % d for d in decls])
351        code += "};\n"
352
353        # close #ifndef __PARAMS__* guard
354        code += "\n#endif\n"
355        return code
356
357    def swig_decl(cls):
358
359        code = '%%module %sParams\n' % cls
360
361        if str(cls) != 'SimObject':
362            base = cls.__bases__[0].type
363        else:
364            base = None
365
366        # The 'dict' attribute restricts us to the params declared in
367        # the object itself, not including inherited params (which
368        # will also be inherited from the base class's param struct
369        # here).
370        params = cls._params.dict.values()
371        ptypes = [p.ptype for p in params]
372
373        # get a list of lists of predeclaration lines
374        predecls = [p.swig_predecls() for p in params]
375        # flatten
376        predecls = reduce(lambda x,y:x+y, predecls, [])
377        # remove redundant lines
378        predecls2 = []
379        for pd in predecls:
380            if pd not in predecls2:
381                predecls2.append(pd)
382        predecls2.sort()
383        code += "\n".join(predecls2)
384        code += "\n\n";
385
386        if base:
387            code += '%%import "python/m5/swig/%sParams.i"\n\n' % base
388
389        code += '%{\n'
390        code += '#include "params/%s.hh"\n' % cls
391        code += '%}\n\n'
392        code += '%%include "params/%s.hh"\n\n' % cls
393
394        return code
395
396# The SimObject class is the root of the special hierarchy.  Most of
397# the code in this class deals with the configuration hierarchy itself
398# (parent/child node relationships).
399class SimObject(object):
400    # Specify metaclass.  Any class inheriting from SimObject will
401    # get this metaclass.
402    __metaclass__ = MetaSimObject
403    type = 'SimObject'
404
405    name = Param.String("Object name")
406
407    # Initialize new instance.  For objects with SimObject-valued
408    # children, we need to recursively clone the classes represented
409    # by those param values as well in a consistent "deep copy"-style
410    # fashion.  That is, we want to make sure that each instance is
411    # cloned only once, and that if there are multiple references to
412    # the same original object, we end up with the corresponding
413    # cloned references all pointing to the same cloned instance.
414    def __init__(self, **kwargs):
415        ancestor = kwargs.get('_ancestor')
416        memo_dict = kwargs.get('_memo')
417        if memo_dict is None:
418            # prepare to memoize any recursively instantiated objects
419            memo_dict = {}
420        elif ancestor:
421            # memoize me now to avoid problems with recursive calls
422            memo_dict[ancestor] = self
423
424        if not ancestor:
425            ancestor = self.__class__
426        ancestor._instantiated = True
427
428        # initialize required attributes
429        self._parent = None
430        self._children = {}
431        self._ccObject = None  # pointer to C++ object
432        self._instantiated = False # really "cloned"
433
434        # Inherit parameter values from class using multidict so
435        # individual value settings can be overridden.
436        self._values = multidict(ancestor._values)
437        # clone SimObject-valued parameters
438        for key,val in ancestor._values.iteritems():
439            if isSimObject(val):
440                setattr(self, key, val(_memo=memo_dict))
441            elif isSimObjectSequence(val) and len(val):
442                setattr(self, key, [ v(_memo=memo_dict) for v in val ])
443        # clone port references.  no need to use a multidict here
444        # since we will be creating new references for all ports.
445        self._port_map = {}
446        for key,val in ancestor._port_map.iteritems():
447            self._port_map[key] = applyOrMap(val, 'clone', memo_dict)
448        # apply attribute assignments from keyword args, if any
449        for key,val in kwargs.iteritems():
450            setattr(self, key, val)
451
452    # "Clone" the current instance by creating another instance of
453    # this instance's class, but that inherits its parameter values
454    # and port mappings from the current instance.  If we're in a
455    # "deep copy" recursive clone, check the _memo dict to see if
456    # we've already cloned this instance.
457    def __call__(self, **kwargs):
458        memo_dict = kwargs.get('_memo')
459        if memo_dict is None:
460            # no memo_dict: must be top-level clone operation.
461            # this is only allowed at the root of a hierarchy
462            if self._parent:
463                raise RuntimeError, "attempt to clone object %s " \
464                      "not at the root of a tree (parent = %s)" \
465                      % (self, self._parent)
466            # create a new dict and use that.
467            memo_dict = {}
468            kwargs['_memo'] = memo_dict
469        elif memo_dict.has_key(self):
470            # clone already done & memoized
471            return memo_dict[self]
472        return self.__class__(_ancestor = self, **kwargs)
473
474    def __getattr__(self, attr):
475        if self._ports.has_key(attr):
476            # return reference that can be assigned to another port
477            # via __setattr__
478            return self._ports[attr].makeRef(self, attr)
479
480        if self._values.has_key(attr):
481            return self._values[attr]
482
483        raise AttributeError, "object '%s' has no attribute '%s'" \
484              % (self.__class__.__name__, attr)
485
486    # Set attribute (called on foo.attr = value when foo is an
487    # instance of class cls).
488    def __setattr__(self, attr, value):
489        # normal processing for private attributes
490        if attr.startswith('_'):
491            object.__setattr__(self, attr, value)
492            return
493
494        if self._ports.has_key(attr):
495            # set up port connection
496            self._ports[attr].connect(self, attr, value)
497            return
498
499        if isSimObjectOrSequence(value) and self._instantiated:
500            raise RuntimeError, \
501                  "cannot set SimObject parameter '%s' after\n" \
502                  "    instance been cloned %s" % (attr, `self`)
503
504        # must be SimObject param
505        param = self._params.get(attr, None)
506        if param:
507            try:
508                value = param.convert(value)
509            except Exception, e:
510                msg = "%s\nError setting param %s.%s to %s\n" % \
511                      (e, self.__class__.__name__, attr, value)
512                e.args = (msg, )
513                raise
514        elif isSimObjectOrSequence(value):
515            pass
516        else:
517            raise AttributeError, "Class %s has no parameter %s" \
518                  % (self.__class__.__name__, attr)
519
520        # clear out old child with this name, if any
521        self.clear_child(attr)
522
523        if isSimObject(value):
524            value.set_path(self, attr)
525        elif isSimObjectSequence(value):
526            value = SimObjVector(value)
527            [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)]
528
529        self._values[attr] = value
530
531    # this hack allows tacking a '[0]' onto parameters that may or may
532    # not be vectors, and always getting the first element (e.g. cpus)
533    def __getitem__(self, key):
534        if key == 0:
535            return self
536        raise TypeError, "Non-zero index '%s' to SimObject" % key
537
538    # clear out children with given name, even if it's a vector
539    def clear_child(self, name):
540        if not self._children.has_key(name):
541            return
542        child = self._children[name]
543        if isinstance(child, SimObjVector):
544            for i in xrange(len(child)):
545                del self._children["s%d" % (name, i)]
546        del self._children[name]
547
548    def add_child(self, name, value):
549        self._children[name] = value
550
551    def set_path(self, parent, name):
552        if not self._parent:
553            self._parent = parent
554            self._name = name
555            parent.add_child(name, self)
556
557    def path(self):
558        if not self._parent:
559            return 'root'
560        ppath = self._parent.path()
561        if ppath == 'root':
562            return self._name
563        return ppath + "." + self._name
564
565    def __str__(self):
566        return self.path()
567
568    def ini_str(self):
569        return self.path()
570
571    def find_any(self, ptype):
572        if isinstance(self, ptype):
573            return self, True
574
575        found_obj = None
576        for child in self._children.itervalues():
577            if isinstance(child, ptype):
578                if found_obj != None and child != found_obj:
579                    raise AttributeError, \
580                          'parent.any matched more than one: %s %s' % \
581                          (found_obj.path, child.path)
582                found_obj = child
583        # search param space
584        for pname,pdesc in self._params.iteritems():
585            if issubclass(pdesc.ptype, ptype):
586                match_obj = self._values[pname]
587                if found_obj != None and found_obj != match_obj:
588                    raise AttributeError, \
589                          'parent.any matched more than one: %s' % obj.path
590                found_obj = match_obj
591        return found_obj, found_obj != None
592
593    def unproxy(self, base):
594        return self
595
596    def print_ini(self):
597        print '[' + self.path() + ']'	# .ini section header
598
599        instanceDict[self.path()] = self
600
601        if hasattr(self, 'type') and not isinstance(self, ParamContext):
602            print 'type=%s' % self.type
603
604        child_names = self._children.keys()
605        child_names.sort()
606        np_child_names = [c for c in child_names \
607                          if not isinstance(self._children[c], ParamContext)]
608        if len(np_child_names):
609            print 'children=%s' % ' '.join(np_child_names)
610
611        param_names = self._params.keys()
612        param_names.sort()
613        for param in param_names:
614            value = self._values.get(param, None)
615            if value != None:
616                if isproxy(value):
617                    try:
618                        value = value.unproxy(self)
619                    except:
620                        print >> sys.stderr, \
621                              "Error in unproxying param '%s' of %s" % \
622                              (param, self.path())
623                        raise
624                    setattr(self, param, value)
625                print '%s=%s' % (param, self._values[param].ini_str())
626
627        print	# blank line between objects
628
629        for child in child_names:
630            self._children[child].print_ini()
631
632    # Call C++ to create C++ object corresponding to this object and
633    # (recursively) all its children
634    def createCCObject(self):
635        self.getCCObject() # force creation
636        for child in self._children.itervalues():
637            child.createCCObject()
638
639    # Get C++ object corresponding to this object, calling C++ if
640    # necessary to construct it.  Does *not* recursively create
641    # children.
642    def getCCObject(self):
643        if not self._ccObject:
644            self._ccObject = -1 # flag to catch cycles in recursion
645            self._ccObject = cc_main.createSimObject(self.path())
646        elif self._ccObject == -1:
647            raise RuntimeError, "%s: recursive call to getCCObject()" \
648                  % self.path()
649        return self._ccObject
650
651    # Create C++ port connections corresponding to the connections in
652    # _port_map (& recursively for all children)
653    def connectPorts(self):
654        for portRef in self._port_map.itervalues():
655            applyOrMap(portRef, 'ccConnect')
656        for child in self._children.itervalues():
657            child.connectPorts()
658
659    def startDrain(self, drain_event, recursive):
660        count = 0
661        # ParamContexts don't serialize
662        if isinstance(self, SimObject) and not isinstance(self, ParamContext):
663            count += self._ccObject.drain(drain_event)
664        if recursive:
665            for child in self._children.itervalues():
666                count += child.startDrain(drain_event, True)
667        return count
668
669    def resume(self):
670        if isinstance(self, SimObject) and not isinstance(self, ParamContext):
671            self._ccObject.resume()
672        for child in self._children.itervalues():
673            child.resume()
674
675    def changeTiming(self, mode):
676        if isinstance(self, System):
677            self._ccObject.setMemoryMode(mode)
678        for child in self._children.itervalues():
679            child.changeTiming(mode)
680
681    def takeOverFrom(self, old_cpu):
682        cpu_ptr = cc_main.convertToBaseCPUPtr(old_cpu._ccObject)
683        self._ccObject.takeOverFrom(cpu_ptr)
684
685    # generate output file for 'dot' to display as a pretty graph.
686    # this code is currently broken.
687    def outputDot(self, dot):
688        label = "{%s|" % self.path
689        if isSimObject(self.realtype):
690            label +=  '%s|' % self.type
691
692        if self.children:
693            # instantiate children in same order they were added for
694            # backward compatibility (else we can end up with cpu1
695            # before cpu0).
696            for c in self.children:
697                dot.add_edge(pydot.Edge(self.path,c.path, style="bold"))
698
699        simobjs = []
700        for param in self.params:
701            try:
702                if param.value is None:
703                    raise AttributeError, 'Parameter with no value'
704
705                value = param.value
706                string = param.string(value)
707            except Exception, e:
708                msg = 'exception in %s:%s\n%s' % (self.name, param.name, e)
709                e.args = (msg, )
710                raise
711
712            if isSimObject(param.ptype) and string != "Null":
713                simobjs.append(string)
714            else:
715                label += '%s = %s\\n' % (param.name, string)
716
717        for so in simobjs:
718            label += "|<%s> %s" % (so, so)
719            dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so,
720                                    tailport="w"))
721        label += '}'
722        dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label))
723
724        # recursively dump out children
725        for c in self.children:
726            c.outputDot(dot)
727
728class ParamContext(SimObject):
729    pass
730
731#####################################################################
732#
733# Proxy object support.
734#
735#####################################################################
736
737class BaseProxy(object):
738    def __init__(self, search_self, search_up):
739        self._search_self = search_self
740        self._search_up = search_up
741        self._multiplier = None
742
743    def __setattr__(self, attr, value):
744        if not attr.startswith('_'):
745            raise AttributeError, 'cannot set attribute on proxy object'
746        super(BaseProxy, self).__setattr__(attr, value)
747
748    # support multiplying proxies by constants
749    def __mul__(self, other):
750        if not isinstance(other, (int, long, float)):
751            raise TypeError, "Proxy multiplier must be integer"
752        if self._multiplier == None:
753            self._multiplier = other
754        else:
755            # support chained multipliers
756            self._multiplier *= other
757        return self
758
759    __rmul__ = __mul__
760
761    def _mulcheck(self, result):
762        if self._multiplier == None:
763            return result
764        return result * self._multiplier
765
766    def unproxy(self, base):
767        obj = base
768        done = False
769
770        if self._search_self:
771            result, done = self.find(obj)
772
773        if self._search_up:
774            while not done:
775                obj = obj._parent
776                if not obj:
777                    break
778                result, done = self.find(obj)
779
780        if not done:
781            raise AttributeError, \
782                  "Can't resolve proxy '%s' of type '%s' from '%s'" % \
783                  (self.path(), self._pdesc.ptype_str, base.path())
784
785        if isinstance(result, BaseProxy):
786            if result == self:
787                raise RuntimeError, "Cycle in unproxy"
788            result = result.unproxy(obj)
789
790        return self._mulcheck(result)
791
792    def getindex(obj, index):
793        if index == None:
794            return obj
795        try:
796            obj = obj[index]
797        except TypeError:
798            if index != 0:
799                raise
800            # if index is 0 and item is not subscriptable, just
801            # use item itself (so cpu[0] works on uniprocessors)
802        return obj
803    getindex = staticmethod(getindex)
804
805    def set_param_desc(self, pdesc):
806        self._pdesc = pdesc
807
808class AttrProxy(BaseProxy):
809    def __init__(self, search_self, search_up, attr):
810        super(AttrProxy, self).__init__(search_self, search_up)
811        self._attr = attr
812        self._modifiers = []
813
814    def __getattr__(self, attr):
815        # python uses __bases__ internally for inheritance
816        if attr.startswith('_'):
817            return super(AttrProxy, self).__getattr__(self, attr)
818        if hasattr(self, '_pdesc'):
819            raise AttributeError, "Attribute reference on bound proxy"
820        self._modifiers.append(attr)
821        return self
822
823    # support indexing on proxies (e.g., Self.cpu[0])
824    def __getitem__(self, key):
825        if not isinstance(key, int):
826            raise TypeError, "Proxy object requires integer index"
827        self._modifiers.append(key)
828        return self
829
830    def find(self, obj):
831        try:
832            val = getattr(obj, self._attr)
833        except:
834            return None, False
835        while isproxy(val):
836            val = val.unproxy(obj)
837        for m in self._modifiers:
838            if isinstance(m, str):
839                val = getattr(val, m)
840            elif isinstance(m, int):
841                val = val[m]
842            else:
843                assert("Item must be string or integer")
844            while isproxy(val):
845                val = val.unproxy(obj)
846        return val, True
847
848    def path(self):
849        p = self._attr
850        for m in self._modifiers:
851            if isinstance(m, str):
852                p += '.%s' % m
853            elif isinstance(m, int):
854                p += '[%d]' % m
855            else:
856                assert("Item must be string or integer")
857        return p
858
859class AnyProxy(BaseProxy):
860    def find(self, obj):
861        return obj.find_any(self._pdesc.ptype)
862
863    def path(self):
864        return 'any'
865
866def isproxy(obj):
867    if isinstance(obj, (BaseProxy, EthernetAddr)):
868        return True
869    elif isinstance(obj, (list, tuple)):
870        for v in obj:
871            if isproxy(v):
872                return True
873    return False
874
875class ProxyFactory(object):
876    def __init__(self, search_self, search_up):
877        self.search_self = search_self
878        self.search_up = search_up
879
880    def __getattr__(self, attr):
881        if attr == 'any':
882            return AnyProxy(self.search_self, self.search_up)
883        else:
884            return AttrProxy(self.search_self, self.search_up, attr)
885
886# global objects for handling proxies
887Parent = ProxyFactory(search_self = False, search_up = True)
888Self = ProxyFactory(search_self = True, search_up = False)
889
890#####################################################################
891#
892# Parameter description classes
893#
894# The _params dictionary in each class maps parameter names to either
895# a Param or a VectorParam object.  These objects contain the
896# parameter description string, the parameter type, and the default
897# value (if any).  The convert() method on these objects is used to
898# force whatever value is assigned to the parameter to the appropriate
899# type.
900#
901# Note that the default values are loaded into the class's attribute
902# space when the parameter dictionary is initialized (in
903# MetaSimObject._new_param()); after that point they aren't used.
904#
905#####################################################################
906
907# Dummy base class to identify types that are legitimate for SimObject
908# parameters.
909class ParamValue(object):
910
911    cxx_predecls = []
912    swig_predecls = []
913
914    # default for printing to .ini file is regular string conversion.
915    # will be overridden in some cases
916    def ini_str(self):
917        return str(self)
918
919    # allows us to blithely call unproxy() on things without checking
920    # if they're really proxies or not
921    def unproxy(self, base):
922        return self
923
924# Regular parameter description.
925class ParamDesc(object):
926    def __init__(self, ptype_str, ptype, *args, **kwargs):
927        self.ptype_str = ptype_str
928        # remember ptype only if it is provided
929        if ptype != None:
930            self.ptype = ptype
931
932        if args:
933            if len(args) == 1:
934                self.desc = args[0]
935            elif len(args) == 2:
936                self.default = args[0]
937                self.desc = args[1]
938            else:
939                raise TypeError, 'too many arguments'
940
941        if kwargs.has_key('desc'):
942            assert(not hasattr(self, 'desc'))
943            self.desc = kwargs['desc']
944            del kwargs['desc']
945
946        if kwargs.has_key('default'):
947            assert(not hasattr(self, 'default'))
948            self.default = kwargs['default']
949            del kwargs['default']
950
951        if kwargs:
952            raise TypeError, 'extra unknown kwargs %s' % kwargs
953
954        if not hasattr(self, 'desc'):
955            raise TypeError, 'desc attribute missing'
956
957    def __getattr__(self, attr):
958        if attr == 'ptype':
959            try:
960                ptype = eval(self.ptype_str, m5.objects.__dict__)
961                if not isinstance(ptype, type):
962                    panic("Param qualifier is not a type: %s" % self.ptype)
963                self.ptype = ptype
964                return ptype
965            except NameError:
966                pass
967        raise AttributeError, "'%s' object has no attribute '%s'" % \
968              (type(self).__name__, attr)
969
970    def convert(self, value):
971        if isinstance(value, BaseProxy):
972            value.set_param_desc(self)
973            return value
974        if not hasattr(self, 'ptype') and isNullPointer(value):
975            # deferred evaluation of SimObject; continue to defer if
976            # we're just assigning a null pointer
977            return value
978        if isinstance(value, self.ptype):
979            return value
980        if isNullPointer(value) and issubclass(self.ptype, SimObject):
981            return value
982        return self.ptype(value)
983
984    def cxx_predecls(self):
985        return self.ptype.cxx_predecls
986
987    def swig_predecls(self):
988        return self.ptype.swig_predecls
989
990    def cxx_decl(self):
991        return '%s %s;' % (self.ptype.cxx_type, self.name)
992
993# Vector-valued parameter description.  Just like ParamDesc, except
994# that the value is a vector (list) of the specified type instead of a
995# single value.
996
997class VectorParamValue(list):
998    def ini_str(self):
999        return ' '.join([v.ini_str() for v in self])
1000
1001    def unproxy(self, base):
1002        return [v.unproxy(base) for v in self]
1003
1004class SimObjVector(VectorParamValue):
1005    def print_ini(self):
1006        for v in self:
1007            v.print_ini()
1008
1009class VectorParamDesc(ParamDesc):
1010    # Convert assigned value to appropriate type.  If the RHS is not a
1011    # list or tuple, it generates a single-element list.
1012    def convert(self, value):
1013        if isinstance(value, (list, tuple)):
1014            # list: coerce each element into new list
1015            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
1016            if isSimObjectSequence(tmp_list):
1017                return SimObjVector(tmp_list)
1018            else:
1019                return VectorParamValue(tmp_list)
1020        else:
1021            # singleton: leave it be (could coerce to a single-element
1022            # list here, but for some historical reason we don't...
1023            return ParamDesc.convert(self, value)
1024
1025    def cxx_predecls(self):
1026        return ['#include <vector>'] + self.ptype.cxx_predecls
1027
1028    def swig_predecls(self):
1029        return ['%include "std_vector.i"'] + self.ptype.swig_predecls
1030
1031    def cxx_decl(self):
1032        return 'std::vector< %s > %s;' % (self.ptype.cxx_type, self.name)
1033
1034class ParamFactory(object):
1035    def __init__(self, param_desc_class, ptype_str = None):
1036        self.param_desc_class = param_desc_class
1037        self.ptype_str = ptype_str
1038
1039    def __getattr__(self, attr):
1040        if self.ptype_str:
1041            attr = self.ptype_str + '.' + attr
1042        return ParamFactory(self.param_desc_class, attr)
1043
1044    # E.g., Param.Int(5, "number of widgets")
1045    def __call__(self, *args, **kwargs):
1046        caller_frame = inspect.currentframe().f_back
1047        ptype = None
1048        try:
1049            ptype = eval(self.ptype_str,
1050                         caller_frame.f_globals, caller_frame.f_locals)
1051            if not isinstance(ptype, type):
1052                raise TypeError, \
1053                      "Param qualifier is not a type: %s" % ptype
1054        except NameError:
1055            # if name isn't defined yet, assume it's a SimObject, and
1056            # try to resolve it later
1057            pass
1058        return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs)
1059
1060Param = ParamFactory(ParamDesc)
1061VectorParam = ParamFactory(VectorParamDesc)
1062
1063# String-valued parameter.  Just mixin the ParamValue class
1064# with the built-in str class.
1065class String(ParamValue,str):
1066    cxx_type = 'std::string'
1067    cxx_predecls = ['#include <string>']
1068    swig_predecls = ['%include "std_string.i"\n' +
1069                     '%apply const std::string& {std::string *};']
1070    pass
1071
1072#####################################################################
1073#
1074# Parameter Types
1075#
1076# Though native Python types could be used to specify parameter types
1077# (the 'ptype' field of the Param and VectorParam classes), it's more
1078# flexible to define our own set of types.  This gives us more control
1079# over how Python expressions are converted to values (via the
1080# __init__() constructor) and how these values are printed out (via
1081# the __str__() conversion method).  Eventually we'll need these types
1082# to correspond to distinct C++ types as well.
1083#
1084#####################################################################
1085
1086# superclass for "numeric" parameter values, to emulate math
1087# operations in a type-safe way.  e.g., a Latency times an int returns
1088# a new Latency object.
1089class NumericParamValue(ParamValue):
1090    def __str__(self):
1091        return str(self.value)
1092
1093    def __float__(self):
1094        return float(self.value)
1095
1096    # hook for bounds checking
1097    def _check(self):
1098        return
1099
1100    def __mul__(self, other):
1101        newobj = self.__class__(self)
1102        newobj.value *= other
1103        newobj._check()
1104        return newobj
1105
1106    __rmul__ = __mul__
1107
1108    def __div__(self, other):
1109        newobj = self.__class__(self)
1110        newobj.value /= other
1111        newobj._check()
1112        return newobj
1113
1114    def __sub__(self, other):
1115        newobj = self.__class__(self)
1116        newobj.value -= other
1117        newobj._check()
1118        return newobj
1119
1120# Metaclass for bounds-checked integer parameters.  See CheckedInt.
1121class CheckedIntType(type):
1122    def __init__(cls, name, bases, dict):
1123        super(CheckedIntType, cls).__init__(name, bases, dict)
1124
1125        # CheckedInt is an abstract base class, so we actually don't
1126        # want to do any processing on it... the rest of this code is
1127        # just for classes that derive from CheckedInt.
1128        if name == 'CheckedInt':
1129            return
1130
1131        if not cls.cxx_predecls:
1132            # most derived types require this, so we just do it here once
1133            cls.cxx_predecls = ['#include "sim/host.hh"']
1134
1135        if not cls.swig_predecls:
1136            # most derived types require this, so we just do it here once
1137            cls.swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
1138                                 '%import "sim/host.hh"']
1139
1140        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
1141            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
1142                panic("CheckedInt subclass %s must define either\n" \
1143                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
1144                      % name);
1145            if cls.unsigned:
1146                cls.min = 0
1147                cls.max = 2 ** cls.size - 1
1148            else:
1149                cls.min = -(2 ** (cls.size - 1))
1150                cls.max = (2 ** (cls.size - 1)) - 1
1151
1152# Abstract superclass for bounds-checked integer parameters.  This
1153# class is subclassed to generate parameter classes with specific
1154# bounds.  Initialization of the min and max bounds is done in the
1155# metaclass CheckedIntType.__init__.
1156class CheckedInt(NumericParamValue):
1157    __metaclass__ = CheckedIntType
1158
1159    def _check(self):
1160        if not self.min <= self.value <= self.max:
1161            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
1162                  (self.min, self.value, self.max)
1163
1164    def __init__(self, value):
1165        if isinstance(value, str):
1166            self.value = toInteger(value)
1167        elif isinstance(value, (int, long, float)):
1168            self.value = long(value)
1169        self._check()
1170
1171class Int(CheckedInt):      cxx_type = 'int';      size = 32; unsigned = False
1172class Unsigned(CheckedInt): cxx_type = 'unsigned'; size = 32; unsigned = True
1173
1174class Int8(CheckedInt):     cxx_type =   'int8_t'; size =  8; unsigned = False
1175class UInt8(CheckedInt):    cxx_type =  'uint8_t'; size =  8; unsigned = True
1176class Int16(CheckedInt):    cxx_type =  'int16_t'; size = 16; unsigned = False
1177class UInt16(CheckedInt):   cxx_type = 'uint16_t'; size = 16; unsigned = True
1178class Int32(CheckedInt):    cxx_type =  'int32_t'; size = 32; unsigned = False
1179class UInt32(CheckedInt):   cxx_type = 'uint32_t'; size = 32; unsigned = True
1180class Int64(CheckedInt):    cxx_type =  'int64_t'; size = 64; unsigned = False
1181class UInt64(CheckedInt):   cxx_type = 'uint64_t'; size = 64; unsigned = True
1182
1183class Counter(CheckedInt):  cxx_type = 'Counter';  size = 64; unsigned = True
1184class Tick(CheckedInt):     cxx_type = 'Tick';     size = 64; unsigned = True
1185class TcpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
1186class UdpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
1187
1188class Percent(CheckedInt):  cxx_type = 'int'; min = 0; max = 100
1189
1190class Float(ParamValue, float):
1191    pass
1192
1193class MemorySize(CheckedInt):
1194    cxx_type = 'uint64_t'
1195    size = 64
1196    unsigned = True
1197    def __init__(self, value):
1198        if isinstance(value, MemorySize):
1199            self.value = value.value
1200        else:
1201            self.value = toMemorySize(value)
1202        self._check()
1203
1204class MemorySize32(CheckedInt):
1205    size = 32
1206    unsigned = True
1207    def __init__(self, value):
1208        if isinstance(value, MemorySize):
1209            self.value = value.value
1210        else:
1211            self.value = toMemorySize(value)
1212        self._check()
1213
1214class Addr(CheckedInt):
1215    cxx_type = 'Addr'
1216    cxx_predecls = ['#include "targetarch/isa_traits.hh"']
1217    size = 64
1218    unsigned = True
1219    def __init__(self, value):
1220        if isinstance(value, Addr):
1221            self.value = value.value
1222        else:
1223            try:
1224                self.value = toMemorySize(value)
1225            except TypeError:
1226                self.value = long(value)
1227        self._check()
1228
1229
1230class MetaRange(type):
1231    def __init__(cls, name, bases, dict):
1232        super(MetaRange, cls).__init__(name, bases, dict)
1233        if name == 'Range':
1234            return
1235        cls.cxx_type = 'Range< %s >' % cls.type.cxx_type
1236        cls.cxx_predecls = \
1237                       ['#include "base/range.hh"'] + cls.type.cxx_predecls
1238
1239class Range(ParamValue):
1240    __metaclass__ = MetaRange
1241    type = Int # default; can be overridden in subclasses
1242    def __init__(self, *args, **kwargs):
1243        def handle_kwargs(self, kwargs):
1244            if 'end' in kwargs:
1245                self.second = self.type(kwargs.pop('end'))
1246            elif 'size' in kwargs:
1247                self.second = self.first + self.type(kwargs.pop('size')) - 1
1248            else:
1249                raise TypeError, "Either end or size must be specified"
1250
1251        if len(args) == 0:
1252            self.first = self.type(kwargs.pop('start'))
1253            handle_kwargs(self, kwargs)
1254
1255        elif len(args) == 1:
1256            if kwargs:
1257                self.first = self.type(args[0])
1258                handle_kwargs(self, kwargs)
1259            elif isinstance(args[0], Range):
1260                self.first = self.type(args[0].first)
1261                self.second = self.type(args[0].second)
1262            else:
1263                self.first = self.type(0)
1264                self.second = self.type(args[0]) - 1
1265
1266        elif len(args) == 2:
1267            self.first = self.type(args[0])
1268            self.second = self.type(args[1])
1269        else:
1270            raise TypeError, "Too many arguments specified"
1271
1272        if kwargs:
1273            raise TypeError, "too many keywords: %s" % kwargs.keys()
1274
1275    def __str__(self):
1276        return '%s:%s' % (self.first, self.second)
1277
1278class AddrRange(Range):
1279    type = Addr
1280
1281class TickRange(Range):
1282    type = Tick
1283
1284# Boolean parameter type.  Python doesn't let you subclass bool, since
1285# it doesn't want to let you create multiple instances of True and
1286# False.  Thus this is a little more complicated than String.
1287class Bool(ParamValue):
1288    cxx_type = 'bool'
1289    def __init__(self, value):
1290        try:
1291            self.value = toBool(value)
1292        except TypeError:
1293            self.value = bool(value)
1294
1295    def __str__(self):
1296        return str(self.value)
1297
1298    def ini_str(self):
1299        if self.value:
1300            return 'true'
1301        return 'false'
1302
1303def IncEthernetAddr(addr, val = 1):
1304    bytes = map(lambda x: int(x, 16), addr.split(':'))
1305    bytes[5] += val
1306    for i in (5, 4, 3, 2, 1):
1307        val,rem = divmod(bytes[i], 256)
1308        bytes[i] = rem
1309        if val == 0:
1310            break
1311        bytes[i - 1] += val
1312    assert(bytes[0] <= 255)
1313    return ':'.join(map(lambda x: '%02x' % x, bytes))
1314
1315class NextEthernetAddr(object):
1316    addr = "00:90:00:00:00:01"
1317
1318    def __init__(self, inc = 1):
1319        self.value = NextEthernetAddr.addr
1320        NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc)
1321
1322class EthernetAddr(ParamValue):
1323    cxx_type = 'Net::EthAddr'
1324    cxx_predecls = ['#include "base/inet.hh"']
1325    swig_predecls = ['class Net::EthAddr;']
1326    def __init__(self, value):
1327        if value == NextEthernetAddr:
1328            self.value = value
1329            return
1330
1331        if not isinstance(value, str):
1332            raise TypeError, "expected an ethernet address and didn't get one"
1333
1334        bytes = value.split(':')
1335        if len(bytes) != 6:
1336            raise TypeError, 'invalid ethernet address %s' % value
1337
1338        for byte in bytes:
1339            if not 0 <= int(byte) <= 256:
1340                raise TypeError, 'invalid ethernet address %s' % value
1341
1342        self.value = value
1343
1344    def unproxy(self, base):
1345        if self.value == NextEthernetAddr:
1346            self.addr = self.value().value
1347        return self
1348
1349    def __str__(self):
1350        if self.value == NextEthernetAddr:
1351            if hasattr(self, 'addr'):
1352                return self.addr
1353            else:
1354                return "NextEthernetAddr (unresolved)"
1355        else:
1356            return self.value
1357
1358# Special class for NULL pointers.  Note the special check in
1359# make_param_value() above that lets these be assigned where a
1360# SimObject is required.
1361# only one copy of a particular node
1362class NullSimObject(object):
1363    __metaclass__ = Singleton
1364
1365    def __call__(cls):
1366        return cls
1367
1368    def _instantiate(self, parent = None, path = ''):
1369        pass
1370
1371    def ini_str(self):
1372        return 'Null'
1373
1374    def unproxy(self, base):
1375        return self
1376
1377    def set_path(self, parent, name):
1378        pass
1379    def __str__(self):
1380        return 'Null'
1381
1382# The only instance you'll ever need...
1383Null = NULL = NullSimObject()
1384
1385# Enumerated types are a little more complex.  The user specifies the
1386# type as Enum(foo) where foo is either a list or dictionary of
1387# alternatives (typically strings, but not necessarily so).  (In the
1388# long run, the integer value of the parameter will be the list index
1389# or the corresponding dictionary value.  For now, since we only check
1390# that the alternative is valid and then spit it into a .ini file,
1391# there's not much point in using the dictionary.)
1392
1393# What Enum() must do is generate a new type encapsulating the
1394# provided list/dictionary so that specific values of the parameter
1395# can be instances of that type.  We define two hidden internal
1396# classes (_ListEnum and _DictEnum) to serve as base classes, then
1397# derive the new type from the appropriate base class on the fly.
1398
1399
1400# Metaclass for Enum types
1401class MetaEnum(type):
1402    def __init__(cls, name, bases, init_dict):
1403        if init_dict.has_key('map'):
1404            if not isinstance(cls.map, dict):
1405                raise TypeError, "Enum-derived class attribute 'map' " \
1406                      "must be of type dict"
1407            # build list of value strings from map
1408            cls.vals = cls.map.keys()
1409            cls.vals.sort()
1410        elif init_dict.has_key('vals'):
1411            if not isinstance(cls.vals, list):
1412                raise TypeError, "Enum-derived class attribute 'vals' " \
1413                      "must be of type list"
1414            # build string->value map from vals sequence
1415            cls.map = {}
1416            for idx,val in enumerate(cls.vals):
1417                cls.map[val] = idx
1418        else:
1419            raise TypeError, "Enum-derived class must define "\
1420                  "attribute 'map' or 'vals'"
1421
1422        cls.cxx_type = name + '::Enum'
1423
1424        super(MetaEnum, cls).__init__(name, bases, init_dict)
1425
1426    # Generate C++ class declaration for this enum type.
1427    # Note that we wrap the enum in a class/struct to act as a namespace,
1428    # so that the enum strings can be brief w/o worrying about collisions.
1429    def cxx_decl(cls):
1430        s = 'struct %s {\n  enum Enum {\n    ' % cls.__name__
1431        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
1432        s += '\n  };\n};\n'
1433        return s
1434
1435# Base class for enum types.
1436class Enum(ParamValue):
1437    __metaclass__ = MetaEnum
1438    vals = []
1439
1440    def __init__(self, value):
1441        if value not in self.map:
1442            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1443                  % (value, self.vals)
1444        self.value = value
1445
1446    def __str__(self):
1447        return self.value
1448
1449ticks_per_sec = None
1450
1451# how big does a rounding error need to be before we warn about it?
1452frequency_tolerance = 0.001  # 0.1%
1453
1454# convert a floting-point # of ticks to integer, and warn if rounding
1455# discards too much precision
1456def tick_check(float_ticks):
1457    if float_ticks == 0:
1458        return 0
1459    int_ticks = int(round(float_ticks))
1460    err = (float_ticks - int_ticks) / float_ticks
1461    if err > frequency_tolerance:
1462        print >> sys.stderr, "Warning: rounding error > tolerance"
1463        print >> sys.stderr, "    %f rounded to %d" % (float_ticks, int_ticks)
1464        #raise ValueError
1465    return int_ticks
1466
1467def getLatency(value):
1468    if isinstance(value, Latency) or isinstance(value, Clock):
1469        return value.value
1470    elif isinstance(value, Frequency) or isinstance(value, RootClock):
1471        return 1 / value.value
1472    elif isinstance(value, str):
1473        try:
1474            return toLatency(value)
1475        except ValueError:
1476            try:
1477                return 1 / toFrequency(value)
1478            except ValueError:
1479                pass # fall through
1480    raise ValueError, "Invalid Frequency/Latency value '%s'" % value
1481
1482
1483class Latency(NumericParamValue):
1484    cxx_type = 'Tick'
1485    cxx_predecls = ['#include "sim/host.hh"']
1486    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
1487                     '%import "sim/host.hh"']
1488    def __init__(self, value):
1489        self.value = getLatency(value)
1490
1491    def __getattr__(self, attr):
1492        if attr in ('latency', 'period'):
1493            return self
1494        if attr == 'frequency':
1495            return Frequency(self)
1496        raise AttributeError, "Latency object has no attribute '%s'" % attr
1497
1498    # convert latency to ticks
1499    def ini_str(self):
1500        return str(tick_check(self.value * ticks_per_sec))
1501
1502class Frequency(NumericParamValue):
1503    cxx_type = 'Tick'
1504    cxx_predecls = ['#include "sim/host.hh"']
1505    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
1506                     '%import "sim/host.hh"']
1507    def __init__(self, value):
1508        self.value = 1 / getLatency(value)
1509
1510    def __getattr__(self, attr):
1511        if attr == 'frequency':
1512            return self
1513        if attr in ('latency', 'period'):
1514            return Latency(self)
1515        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1516
1517    # convert frequency to ticks per period
1518    def ini_str(self):
1519        return self.period.ini_str()
1520
1521# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz).
1522# We can't inherit from Frequency because we don't want it to be directly
1523# assignable to a regular Frequency parameter.
1524class RootClock(ParamValue):
1525    cxx_type = 'Tick'
1526    cxx_predecls = ['#include "sim/host.hh"']
1527    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
1528                     '%import "sim/host.hh"']
1529    def __init__(self, value):
1530        self.value = 1 / getLatency(value)
1531
1532    def __getattr__(self, attr):
1533        if attr == 'frequency':
1534            return Frequency(self)
1535        if attr in ('latency', 'period'):
1536            return Latency(self)
1537        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1538
1539    def ini_str(self):
1540        return str(tick_check(self.value))
1541
1542# A generic frequency and/or Latency value.  Value is stored as a latency,
1543# but to avoid ambiguity this object does not support numeric ops (* or /).
1544# An explicit conversion to a Latency or Frequency must be made first.
1545class Clock(ParamValue):
1546    cxx_type = 'Tick'
1547    cxx_predecls = ['#include "sim/host.hh"']
1548    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
1549                     '%import "sim/host.hh"']
1550    def __init__(self, value):
1551        self.value = getLatency(value)
1552
1553    def __getattr__(self, attr):
1554        if attr == 'frequency':
1555            return Frequency(self)
1556        if attr in ('latency', 'period'):
1557            return Latency(self)
1558        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1559
1560    def ini_str(self):
1561        return self.period.ini_str()
1562
1563class NetworkBandwidth(float,ParamValue):
1564    cxx_type = 'float'
1565    def __new__(cls, value):
1566        val = toNetworkBandwidth(value) / 8.0
1567        return super(cls, NetworkBandwidth).__new__(cls, val)
1568
1569    def __str__(self):
1570        return str(self.val)
1571
1572    def ini_str(self):
1573        return '%f' % (ticks_per_sec / float(self))
1574
1575class MemoryBandwidth(float,ParamValue):
1576    cxx_type = 'float'
1577    def __new__(self, value):
1578        val = toMemoryBandwidth(value)
1579        return super(cls, MemoryBandwidth).__new__(cls, val)
1580
1581    def __str__(self):
1582        return str(self.val)
1583
1584    def ini_str(self):
1585        return '%f' % (ticks_per_sec / float(self))
1586
1587#
1588# "Constants"... handy aliases for various values.
1589#
1590
1591# Some memory range specifications use this as a default upper bound.
1592MaxAddr = Addr.max
1593MaxTick = Tick.max
1594AllMemory = AddrRange(0, MaxAddr)
1595
1596
1597#####################################################################
1598#
1599# Port objects
1600#
1601# Ports are used to interconnect objects in the memory system.
1602#
1603#####################################################################
1604
1605# Port reference: encapsulates a reference to a particular port on a
1606# particular SimObject.
1607class PortRef(object):
1608    def __init__(self, simobj, name, isVec):
1609        assert(isSimObject(simobj))
1610        self.simobj = simobj
1611        self.name = name
1612        self.index = -1
1613        self.isVec = isVec # is this a vector port?
1614        self.peer = None   # not associated with another port yet
1615        self.ccConnected = False # C++ port connection done?
1616
1617    # Set peer port reference.  Called via __setattr__ as a result of
1618    # a port assignment, e.g., "obj1.port1 = obj2.port2".
1619    def setPeer(self, other):
1620        if self.isVec:
1621            curMap = self.simobj._port_map.get(self.name, [])
1622            self.index = len(curMap)
1623            curMap.append(other)
1624        else:
1625            curMap = self.simobj._port_map.get(self.name)
1626            if curMap and not self.isVec:
1627                print "warning: overwriting port", self.simobj, self.name
1628            curMap = other
1629        self.simobj._port_map[self.name] = curMap
1630        self.peer = other
1631
1632    def clone(self, memo):
1633        newRef = copy.copy(self)
1634        assert(isSimObject(newRef.simobj))
1635        newRef.simobj = newRef.simobj(_memo=memo)
1636        # Tricky: if I'm the *second* PortRef in the pair to be
1637        # cloned, then my peer is still in the middle of its clone
1638        # method, and thus hasn't returned to its owner's
1639        # SimObject.__init__ to get installed in _port_map.  As a
1640        # result I have no way of finding the *new* peer object.  So I
1641        # mark myself as "waiting" for my peer, and I let the *first*
1642        # PortRef clone call set up both peer pointers after I return.
1643        newPeer = newRef.simobj._port_map.get(self.name)
1644        if newPeer:
1645            if self.isVec:
1646                assert(self.index != -1)
1647                newPeer = newPeer[self.index]
1648            # other guy is all set up except for his peer pointer
1649            assert(newPeer.peer == -1) # peer must be waiting for handshake
1650            newPeer.peer = newRef
1651            newRef.peer = newPeer
1652        else:
1653            # other guy is in clone; just wait for him to do the work
1654            newRef.peer = -1 # mark as waiting for handshake
1655        return newRef
1656
1657    # Call C++ to create corresponding port connection between C++ objects
1658    def ccConnect(self):
1659        if self.ccConnected: # already done this
1660            return
1661        peer = self.peer
1662        cc_main.connectPorts(self.simobj.getCCObject(), self.name, self.index,
1663                             peer.simobj.getCCObject(), peer.name, peer.index)
1664        self.ccConnected = True
1665        peer.ccConnected = True
1666
1667# Port description object.  Like a ParamDesc object, this represents a
1668# logical port in the SimObject class, not a particular port on a
1669# SimObject instance.  The latter are represented by PortRef objects.
1670class Port(object):
1671    def __init__(self, desc):
1672        self.desc = desc
1673        self.isVec = False
1674
1675    # Generate a PortRef for this port on the given SimObject with the
1676    # given name
1677    def makeRef(self, simobj, name):
1678        return PortRef(simobj, name, self.isVec)
1679
1680    # Connect an instance of this port (on the given SimObject with
1681    # the given name) with the port described by the supplied PortRef
1682    def connect(self, simobj, name, ref):
1683        if not isinstance(ref, PortRef):
1684            raise TypeError, \
1685                  "assigning non-port reference port '%s'" % name
1686        myRef = self.makeRef(simobj, name)
1687        myRef.setPeer(ref)
1688        ref.setPeer(myRef)
1689
1690# VectorPort description object.  Like Port, but represents a vector
1691# of connections (e.g., as on a Bus).
1692class VectorPort(Port):
1693    def __init__(self, desc):
1694        Port.__init__(self, desc)
1695        self.isVec = True
1696
1697#####################################################################
1698
1699# __all__ defines the list of symbols that get exported when
1700# 'from config import *' is invoked.  Try to keep this reasonably
1701# short to avoid polluting other namespaces.
1702__all__ = ['SimObject', 'ParamContext', 'Param', 'VectorParam',
1703           'Parent', 'Self',
1704           'Enum', 'Bool', 'String', 'Float',
1705           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1706           'Int32', 'UInt32', 'Int64', 'UInt64',
1707           'Counter', 'Addr', 'Tick', 'Percent',
1708           'TcpPort', 'UdpPort', 'EthernetAddr',
1709           'MemorySize', 'MemorySize32',
1710           'Latency', 'Frequency', 'RootClock', 'Clock',
1711           'NetworkBandwidth', 'MemoryBandwidth',
1712           'Range', 'AddrRange', 'TickRange',
1713           'MaxAddr', 'MaxTick', 'AllMemory',
1714           'Null', 'NULL',
1715           'NextEthernetAddr',
1716           'Port', 'VectorPort']
1717
1718