SimObject.py revision 1378
1# Copyright (c) 2004 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
29noDot = False
30try:
31    import pydot
32except:
33    noDot = True
34
35env = {}
36env.update(os.environ)
37
38def panic(*args, **kwargs):
39    sys.exit(*args, **kwargs)
40
41def AddToPath(path):
42    path = os.path.realpath(path)
43    if os.path.isdir(path):
44        sys.path.append(path)
45
46def Import(path):
47    AddToPath(os.path.dirname(path))
48    exec('from m5config import *')
49    mpy_exec(file(path, 'r'))
50
51def issequence(value):
52    return isinstance(value, tuple) or isinstance(value, list)
53
54class Singleton(type):
55    def __call__(cls, *args, **kwargs):
56        if hasattr(cls, '_instance'):
57            return cls._instance
58
59        cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
60        return cls._instance
61
62#####################################################################
63#
64# M5 Python Configuration Utility
65#
66# The basic idea is to write simple Python programs that build Python
67# objects corresponding to M5 SimObjects for the deisred simulation
68# configuration.  For now, the Python emits a .ini file that can be
69# parsed by M5.  In the future, some tighter integration between M5
70# and the Python interpreter may allow bypassing the .ini file.
71#
72# Each SimObject class in M5 is represented by a Python class with the
73# same name.  The Python inheritance tree mirrors the M5 C++ tree
74# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
75# SimObjects inherit from a single SimObject base class).  To specify
76# an instance of an M5 SimObject in a configuration, the user simply
77# instantiates the corresponding Python object.  The parameters for
78# that SimObject are given by assigning to attributes of the Python
79# object, either using keyword assignment in the constructor or in
80# separate assignment statements.  For example:
81#
82# cache = BaseCache('my_cache', root, size=64*K)
83# cache.hit_latency = 3
84# cache.assoc = 8
85#
86# (The first two constructor arguments specify the name of the created
87# cache and its parent node in the hierarchy.)
88#
89# The magic lies in the mapping of the Python attributes for SimObject
90# classes to the actual SimObject parameter specifications.  This
91# allows parameter validity checking in the Python code.  Continuing
92# the example above, the statements "cache.blurfl=3" or
93# "cache.assoc='hello'" would both result in runtime errors in Python,
94# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
95# parameter requires an integer, respectively.  This magic is done
96# primarily by overriding the special __setattr__ method that controls
97# assignment to object attributes.
98#
99# The Python module provides another class, ConfigNode, which is a
100# superclass of SimObject.  ConfigNode implements the parent/child
101# relationship for building the configuration hierarchy tree.
102# Concrete instances of ConfigNode can be used to group objects in the
103# hierarchy, but do not correspond to SimObjects themselves (like a
104# .ini section with "children=" but no "type=".
105#
106# Once a set of Python objects have been instantiated in a hierarchy,
107# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
108# will generate a .ini file.  See simple-4cpu.py for an example
109# (corresponding to m5-test/simple-4cpu.ini).
110#
111#####################################################################
112
113#####################################################################
114#
115# ConfigNode/SimObject classes
116#
117# The Python class hierarchy rooted by ConfigNode (which is the base
118# class of SimObject, which in turn is the base class of all other M5
119# SimObject classes) has special attribute behavior.  In general, an
120# object in this hierarchy has three categories of attribute-like
121# things:
122#
123# 1. Regular Python methods and variables.  These must start with an
124# underscore to be treated normally.
125#
126# 2. SimObject parameters.  These values are stored as normal Python
127# attributes, but all assignments to these attributes are checked
128# against the pre-defined set of parameters stored in the class's
129# _params dictionary.  Assignments to attributes that do not
130# correspond to predefined parameters, or that are not of the correct
131# type, incur runtime errors.
132#
133# 3. Hierarchy children.  The child nodes of a ConfigNode are stored
134# in the node's _children dictionary, but can be accessed using the
135# Python attribute dot-notation (just as they are printed out by the
136# simulator).  Children cannot be created using attribute assigment;
137# they must be added by specifying the parent node in the child's
138# constructor or using the '+=' operator.
139
140# The SimObject parameters are the most complex, for a few reasons.
141# First, both parameter descriptions and parameter values are
142# inherited.  Thus parameter description lookup must go up the
143# inheritance chain like normal attribute lookup, but this behavior
144# must be explicitly coded since the lookup occurs in each class's
145# _params attribute.  Second, because parameter values can be set
146# on SimObject classes (to implement default values), the parameter
147# checking behavior must be enforced on class attribute assignments as
148# well as instance attribute assignments.  Finally, because we allow
149# class specialization via inheritance (e.g., see the L1Cache class in
150# the simple-4cpu.py example), we must do parameter checking even on
151# class instantiation.  To provide all these features, we use a
152# metaclass to define most of the SimObject parameter behavior for
153# this class hierarchy.
154#
155#####################################################################
156
157class Proxy(object):
158    def __init__(self, path = ()):
159        self._object = None
160        self._path = path
161
162    def __getattr__(self, attr):
163        return Proxy(self._path + (attr, ))
164
165    def __setattr__(self, attr, value):
166        if not attr.startswith('_'):
167            raise AttributeError, 'cannot set attribute %s' % attr
168        super(Proxy, self).__setattr__(attr, value)
169
170    def _convert(self):
171        obj = self._object
172        for attr in self._path:
173            obj = obj.__getattribute__(attr)
174        return obj
175
176Super = Proxy()
177
178def isSubClass(value, cls):
179    try:
180        return issubclass(value, cls)
181    except:
182        return False
183
184def isParam(self):
185    return isinstance(self, _Param)
186
187def isConfigNode(value):
188    try:
189        return issubclass(value, ConfigNode)
190    except:
191        return False
192
193def isSimObject(value):
194    try:
195        return issubclass(value, SimObject)
196    except:
197        return False
198
199def isSimObjSequence(value):
200    if not issequence(value):
201        return False
202
203    for val in value:
204        if not isNullPointer(val) and not isConfigNode(val):
205            return False
206
207    return True
208
209def isParamContext(value):
210    try:
211        return issubclass(value, ParamContext)
212    except:
213        return False
214
215
216class_decorator = '_M5M5_SIMOBJECT_'
217expr_decorator = '_M5M5_EXPRESSION_'
218dot_decorator = '_M5M5_DOT_'
219
220# The metaclass for ConfigNode (and thus for everything that derives
221# from ConfigNode, including SimObject).  This class controls how new
222# classes that derive from ConfigNode are instantiated, and provides
223# inherited class behavior (just like a class controls how instances
224# of that class are instantiated, and provides inherited instance
225# behavior).
226class MetaConfigNode(type):
227    keywords = { 'abstract' : types.BooleanType,
228                 'check' : types.FunctionType,
229                 'type' : (types.NoneType, types.StringType) }
230
231    # __new__ is called before __init__, and is where the statements
232    # in the body of the class definition get loaded into the class's
233    # __dict__.  We intercept this to filter out parameter assignments
234    # and only allow "private" attributes to be passed to the base
235    # __new__ (starting with underscore).
236    def __new__(mcls, name, bases, dict):
237        priv = { 'abstract' : False,
238                 # initialize _params and _values dicts to empty
239                 '_params' : {},
240                 '_values' : {},
241                 '_disable' : {} }
242
243        for key,val in dict.items():
244            del dict[key]
245
246            if key.startswith(expr_decorator):
247                key = key[len(expr_decorator):]
248
249            if mcls.keywords.has_key(key):
250                if not isinstance(val, mcls.keywords[key]):
251                    raise TypeError, \
252                          'keyword %s has the wrong type %s should be %s' % \
253                          (key, type(val), mcls.keywords[key])
254
255                if isinstance(val, types.FunctionType):
256                    val = classmethod(val)
257                priv[key] = val
258
259            elif key.startswith('_'):
260                priv[key] = val
261
262            elif not isNullPointer(val) and isConfigNode(val):
263                dict[key] = val()
264
265            elif isSimObjSequence(val):
266                dict[key] = [ v() for v in val ]
267
268            else:
269                dict[key] = val
270
271        # If your parent has a value in it that's a config node, clone it.
272        for base in bases:
273            if not isConfigNode(base):
274                continue
275
276            for key,value in base._values.iteritems():
277                if dict.has_key(key):
278                    continue
279
280                if isConfigNode(value):
281                    priv['_values'][key] = value()
282                elif isSimObjSequence(value):
283                    priv['_values'][key] = [ val() for val in value ]
284
285        # entries left in dict will get passed to __init__, where we'll
286        # deal with them as params.
287        return super(MetaConfigNode, mcls).__new__(mcls, name, bases, priv)
288
289    # initialization: start out with an empty _params dict (makes life
290    # simpler if we can assume _params is always valid).
291    def __init__(cls, name, bases, dict):
292        super(MetaConfigNode, cls).__init__(cls, name, bases, {})
293
294        cls._bases = [c for c in cls.__mro__ if isConfigNode(c)]
295
296        # initialize attributes with values from class definition
297        for key,value in dict.iteritems():
298            key = key.split(dot_decorator)
299            c = cls
300            for item in key[:-1]:
301                c = getattr(c, item)
302            setattr(c, key[-1], value)
303
304    def _isvalue(cls, name):
305        for c in cls._bases:
306            if c._params.has_key(name):
307                return True
308
309        for c in cls._bases:
310            if c._values.has_key(name):
311                return True
312
313        return False
314
315    # generator that iterates across all parameters for this class and
316    # all classes it inherits from
317    def _getparams(cls):
318        params = {}
319        for c in cls._bases:
320            for p,v in c._params.iteritems():
321                if not params.has_key(p):
322                    params[p] = v
323        return params
324
325    # Lookup a parameter description by name in the given class.
326    def _getparam(cls, name, default = AttributeError):
327        for c in cls._bases:
328            if c._params.has_key(name):
329                return c._params[name]
330        if isSubClass(default, Exception):
331            raise default, \
332                  "object '%s' has no attribute '%s'" % (cls.__name__, name)
333        else:
334            return default
335
336    def _setparam(cls, name, value):
337        cls._params[name] = value
338
339    def _hasvalue(cls, name):
340        for c in cls._bases:
341            if c._values.has_key(name):
342                return True
343
344        return False
345
346    def _getvalues(cls):
347        values = {}
348        for i,c in enumerate(cls._bases):
349            for p,v in c._values.iteritems():
350                if not values.has_key(p):
351                    values[p] = v
352        return values
353
354    def _getvalue(cls, name, default = AttributeError):
355        value = None
356        for c in cls._bases:
357            if c._values.has_key(name):
358                value = c._values[name]
359                break
360        if value is not None:
361            return value
362
363        param = cls._getparam(name, None)
364        if param is not None and hasattr(param, 'default'):
365            param.valid(param.default)
366            value = param.default
367            cls._setvalue(name, value)
368            return value
369
370        if isSubClass(default, Exception):
371            raise default, 'value for %s not found' % name
372        else:
373            return default
374
375    def _setvalue(cls, name, value):
376        cls._values[name] = value
377
378    def _getdisable(cls, name):
379        for c in cls._bases:
380            if c._disable.has_key(name):
381                return c._disable[name]
382        return False
383
384    def _setdisable(cls, name, value):
385        cls._disable[name] = value
386
387    def __getattr__(cls, attr):
388        if cls._isvalue(attr):
389            return Value(cls, attr)
390
391        raise AttributeError, \
392              "object '%s' has no attribute '%s'" % (cls.__name__, cls)
393
394    # Set attribute (called on foo.attr = value when foo is an
395    # instance of class cls).
396    def __setattr__(cls, attr, value):
397        # normal processing for private attributes
398        if attr.startswith('_'):
399            type.__setattr__(cls, attr, value)
400            return
401
402        if cls.keywords.has_key(attr):
403            raise TypeError, \
404                  "keyword '%s' can only be set in a simobj definition" % attr
405
406        if isParam(value):
407            cls._setparam(attr, value)
408            return
409
410        # must be SimObject param
411        param = cls._getparam(attr, None)
412        if param:
413            # It's ok: set attribute by delegating to 'object' class.
414            # Note the use of param.make_value() to verify/canonicalize
415            # the assigned value
416            param.valid(value)
417            cls._setvalue(attr, value)
418        elif isConfigNode(value) or isSimObjSequence(value):
419            cls._setvalue(attr, value)
420        else:
421            for p,v in cls._getparams().iteritems():
422                print p,v
423            raise AttributeError, \
424                  "Class %s has no parameter %s" % (cls.__name__, attr)
425
426    def add_child(cls, instance, name, child):
427        if isNullPointer(child) or instance.top_child_names.has_key(name):
428            return
429
430        if issequence(child):
431            kid = []
432            for i,c in enumerate(child):
433                n = '%s%d' % (name, i)
434                k = c.instantiate(n, instance)
435
436                instance.children.append(k)
437                instance.child_names[n] = k
438                instance.child_objects[c] = k
439                kid.append(k)
440        else:
441            kid = child.instantiate(name, instance)
442            instance.children.append(kid)
443            instance.child_names[name] = kid
444            instance.child_objects[child] = kid
445
446        instance.top_child_names[name] = kid
447
448    # Print instance info to .ini file.
449    def instantiate(cls, name, parent = None):
450        instance = Node(name, cls, cls.type, parent, isParamContext(cls))
451
452        if hasattr(cls, 'check'):
453            cls.check()
454
455        for key,value in cls._getvalues().iteritems():
456            if cls._getdisable(key):
457                continue
458
459            if isConfigNode(value):
460                cls.add_child(instance, key, value)
461            if issequence(value):
462                list = [ v for v in value if isConfigNode(v) ]
463                if len(list):
464                    cls.add_child(instance, key, list)
465
466        for pname,param in cls._getparams().iteritems():
467            try:
468                if cls._getdisable(pname):
469                    continue
470
471                try:
472                    value = cls._getvalue(pname)
473                except:
474                    print 'Error getting %s' % pname
475                    raise
476
477                if isConfigNode(value):
478                    value = instance.child_objects[value]
479                elif issequence(value):
480                    v = []
481                    for val in value:
482                        if isConfigNode(val):
483                            v.append(instance.child_objects[val])
484                        else:
485                            v.append(val)
486                    value = v
487
488                p = NodeParam(pname, param, value)
489                instance.params.append(p)
490                instance.param_names[pname] = p
491            except:
492                print 'Exception while evaluating %s.%s' % \
493                      (instance.path, pname)
494                raise
495
496        return instance
497
498    def _convert(cls, value):
499        realvalue = value
500        if isinstance(value, Node):
501            realvalue = value.realtype
502
503        if isinstance(realvalue, Proxy):
504            return value
505
506        if realvalue == None or isNullPointer(realvalue):
507            return value
508
509        if isSubClass(realvalue, cls):
510            return value
511
512        raise TypeError, 'object %s type %s wrong type, should be %s' % \
513              (repr(realvalue), realvalue, cls)
514
515    def _string(cls, value):
516        if isNullPointer(value):
517            return 'Null'
518        return Node._string(value)
519
520# The ConfigNode class is the root of the special hierarchy.  Most of
521# the code in this class deals with the configuration hierarchy itself
522# (parent/child node relationships).
523class ConfigNode(object):
524    # Specify metaclass.  Any class inheriting from ConfigNode will
525    # get this metaclass.
526    __metaclass__ = MetaConfigNode
527    type = None
528
529    def __new__(cls, **kwargs):
530        return MetaConfigNode(cls.__name__, (cls, ), kwargs)
531
532    # Set attribute.  All attribute assignments go through here.  Must
533    # be private attribute (starts with '_') or valid parameter entry.
534    # Basically identical to MetaConfigClass.__setattr__(), except
535    # this sets attributes on specific instances rather than on classes.
536    #def __setattr__(self, attr, value):
537    #    if attr.startswith('_'):
538    #        object.__setattr__(self, attr, value)
539    #        return
540        # not private; look up as param
541    #    param = self.__class__.lookup_param(attr)
542    #    if not param:
543    #        raise AttributeError, \
544    #              "Class %s has no parameter %s" \
545    #              % (self.__class__.__name__, attr)
546        # It's ok: set attribute by delegating to 'object' class.
547        # Note the use of param.make_value() to verify/canonicalize
548        # the assigned value.
549    #    v = param.convert(value)
550    #    object.__setattr__(self, attr, v)
551
552class ParamContext(ConfigNode):
553    pass
554
555# SimObject is a minimal extension of ConfigNode, implementing a
556# hierarchy node that corresponds to an M5 SimObject.  It prints out a
557# "type=" line to indicate its SimObject class, prints out the
558# assigned parameters corresponding to its class, and allows
559# parameters to be set by keyword in the constructor.  Note that most
560# of the heavy lifting for the SimObject param handling is done in the
561# MetaConfigNode metaclass.
562class SimObject(ConfigNode):
563    def _sim_code(cls):
564        name = cls.__name__
565        param_names = cls._params.keys()
566        param_names.sort()
567        code = "BEGIN_DECLARE_SIM_OBJECT_PARAMS(%s)\n" % name
568        decls = ["  " + cls._params[pname].sim_decl(pname) \
569                 for pname in param_names]
570        code += "\n".join(decls) + "\n"
571        code += "END_DECLARE_SIM_OBJECT_PARAMS(%s)\n\n" % name
572        code += "BEGIN_INIT_SIM_OBJECT_PARAMS(%s)\n" % name
573        inits = ["  " + cls._params[pname].sim_init(pname) \
574                 for pname in param_names]
575        code += ",\n".join(inits) + "\n"
576        code += "END_INIT_SIM_OBJECT_PARAMS(%s)\n\n" % name
577        return code
578    _sim_code = classmethod(_sim_code)
579
580class NodeParam(object):
581    def __init__(self, name, param, value):
582        self.name = name
583        self.param = param
584        self.ptype = param.ptype
585        self.convert = param.convert
586        self.string = param.string
587        self.value = value
588
589class Node(object):
590    all = {}
591    def __init__(self, name, realtype, type, parent, paramcontext):
592        self.name = name
593        self.realtype = realtype
594        self.type = type
595        self.parent = parent
596        self.children = []
597        self.child_names = {}
598        self.child_objects = {}
599        self.top_child_names = {}
600        self.params = []
601        self.param_names = {}
602        self.paramcontext = paramcontext
603
604        path = [ self.name ]
605        node = self.parent
606        while node is not None:
607            if node.name != 'root':
608                path.insert(0, node.name)
609            else:
610                assert(node.parent is None)
611            node = node.parent
612        self.path = '.'.join(path)
613
614    def find(self, realtype, path):
615        rtype = eval(realtype)
616        if not path:
617            if issubclass(self.realtype, rtype):
618                return self, True
619
620            obj = None
621            for child in self.children:
622                if issubclass(child.realtype, rtype):
623                    if obj is not None:
624                        raise AttributeError, \
625                              'Super matched more than one: %s %s' % \
626                              (obj.path, child.path)
627                    obj = child
628            return obj, obj is not None
629
630        try:
631            obj = self
632            for node in path[:-1]:
633                obj = obj.child_names[node]
634
635            last = path[-1]
636            if obj.child_names.has_key(last):
637                value = obj.child_names[last]
638                if issubclass(value.realtype, rtype):
639                    return value, True
640            elif obj.param_names.has_key(last):
641                value = obj.param_names[last]
642                rtype._convert(value.value)
643                return value.value, True
644        except KeyError:
645            pass
646
647        return None, False
648
649    def unproxy(self, ptype, value):
650        if not isinstance(value, Proxy):
651            return value
652
653        if value is None:
654            raise AttributeError, 'Error while fixing up %s' % self.path
655
656        obj = self
657        done = False
658        while not done:
659            if obj is None:
660                raise AttributeError, \
661                      'Parent of %s type %s not found at path %s' \
662                      % (self.name, ptype, value._path)
663            found, done = obj.find(ptype, value._path)
664            if isinstance(found, Proxy):
665                done = False
666            obj = obj.parent
667
668        return found
669
670    def fixup(self):
671        self.all[self.path] = self
672
673        for param in self.params:
674            ptype = param.ptype
675            pval = param.value
676
677            try:
678                if issequence(pval):
679                    param.value = [ self.unproxy(ptype, pv) for pv in pval ]
680                else:
681                    param.value = self.unproxy(ptype, pval)
682            except:
683                print 'Error while fixing up %s:%s' % (self.path, param.name)
684                raise
685
686        for child in self.children:
687            assert(child != self)
688            child.fixup()
689
690    # print type and parameter values to .ini file
691    def display(self):
692        print '[' + self.path + ']'	# .ini section header
693
694        if isSimObject(self.realtype):
695            print 'type = %s' % self.type
696
697        if self.children:
698            # instantiate children in same order they were added for
699            # backward compatibility (else we can end up with cpu1
700            # before cpu0).
701            children = [ c.name for c in self.children if not c.paramcontext]
702            print 'children =', ' '.join(children)
703
704        for param in self.params:
705            try:
706                if param.value is None:
707                    raise AttributeError, 'Parameter with no value'
708
709                value = param.convert(param.value)
710                string = param.string(value)
711            except:
712                print 'exception in %s:%s' % (self.path, param.name)
713                raise
714
715            print '%s = %s' % (param.name, string)
716
717        print
718
719        # recursively dump out children
720        for c in self.children:
721            c.display()
722
723    # print type and parameter values to .ini file
724    def outputDot(self, dot):
725
726
727        label = "{%s|" % self.path
728        if isSimObject(self.realtype):
729            label +=  '%s|' % self.type
730
731        if self.children:
732            # instantiate children in same order they were added for
733            # backward compatibility (else we can end up with cpu1
734            # before cpu0).
735            for c in self.children:
736                dot.add_edge(pydot.Edge(self.path,c.path, style="bold"))
737
738        simobjs = []
739        for param in self.params:
740            try:
741                if param.value is None:
742                    raise AttributeError, 'Parameter with no value'
743
744                value = param.convert(param.value)
745                string = param.string(value)
746            except:
747                print 'exception in %s:%s' % (self.name, param.name)
748                raise
749            ptype = eval(param.ptype)
750            if isConfigNode(ptype) and string != "Null":
751                simobjs.append(string)
752            else:
753                label += '%s = %s\\n' % (param.name, string)
754
755        for so in simobjs:
756            label += "|<%s> %s" % (so, so)
757            dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, tailport="w"))
758        label += '}'
759        dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label))
760
761        # recursively dump out children
762        for c in self.children:
763            c.outputDot(dot)
764
765    def _string(cls, value):
766        if not isinstance(value, Node):
767            raise AttributeError, 'expecting %s got %s' % (Node, value)
768        return value.path
769    _string = classmethod(_string)
770
771#####################################################################
772#
773# Parameter description classes
774#
775# The _params dictionary in each class maps parameter names to
776# either a Param or a VectorParam object.  These objects contain the
777# parameter description string, the parameter type, and the default
778# value (loaded from the PARAM section of the .odesc files).  The
779# _convert() method on these objects is used to force whatever value
780# is assigned to the parameter to the appropriate type.
781#
782# Note that the default values are loaded into the class's attribute
783# space when the parameter dictionary is initialized (in
784# MetaConfigNode._setparams()); after that point they aren't used.
785#
786#####################################################################
787
788def isNullPointer(value):
789    return isinstance(value, NullSimObject)
790
791class Value(object):
792    def __init__(self, obj, attr):
793        super(Value, self).__setattr__('attr', attr)
794        super(Value, self).__setattr__('obj', obj)
795
796    def _getattr(self):
797        return self.obj._getvalue(self.attr)
798
799    def __setattr__(self, attr, value):
800        if attr == 'disable':
801            self.obj._setdisable(self.attr, value)
802        else:
803            setattr(self._getattr(), attr, value)
804
805    def __getattr__(self, attr):
806        if attr == 'disable':
807            return self.obj._getdisable(self.attr)
808        else:
809            return getattr(self._getattr(), attr)
810
811    def __getitem__(self, index):
812        return self._getattr().__getitem__(index)
813
814    def __call__(self, *args, **kwargs):
815        return self._getattr().__call__(*args, **kwargs)
816
817    def __nonzero__(self):
818        return bool(self._getattr())
819
820    def __str__(self):
821        return str(self._getattr())
822
823# Regular parameter.
824class _Param(object):
825    def __init__(self, ptype, *args, **kwargs):
826        self.ptype = ptype
827
828        if args:
829            if len(args) == 1:
830                self.desc = args[0]
831            elif len(args) == 2:
832                self.default = args[0]
833                self.desc = args[1]
834            else:
835                raise TypeError, 'too many arguments'
836
837        if kwargs.has_key('desc'):
838            assert(not hasattr(self, 'desc'))
839            self.desc = kwargs['desc']
840            del kwargs['desc']
841
842        if kwargs.has_key('default'):
843            assert(not hasattr(self, 'default'))
844            self.default = kwargs['default']
845            del kwargs['default']
846
847        if kwargs:
848            raise TypeError, 'extra unknown kwargs %s' % kwargs
849
850        if not hasattr(self, 'desc'):
851            raise TypeError, 'desc attribute missing'
852
853    def valid(self, value):
854        if not isinstance(value, Proxy):
855            ptype = eval(self.ptype)
856            ptype._convert(value)
857
858    def convert(self, value):
859        ptype = eval(self.ptype)
860        return ptype._convert(value)
861
862    def string(self, value):
863        ptype = eval(self.ptype)
864        return ptype._string(value)
865
866    def get(self, name, instance, owner):
867        # nothing to do if None or already correct type.  Also allow NULL
868        # pointer to be assigned where a SimObject is expected.
869        try:
870            if value == None or isinstance(value, self.ptype) or \
871                   isConfigNode(self.ptype) and \
872                   (isNullPointer(value) or issubclass(value, self.ptype)):
873                return value
874
875        except TypeError:
876            # this type conversion will raise an exception if it's illegal
877            return self.ptype(value)
878
879    def set(self, name, instance, value):
880        instance.__dict__[name] = value
881
882    def sim_decl(self, name):
883        return 'Param<%s> %s;' % (self.ptype.__name__, name)
884
885    def sim_init(self, name):
886        if self.default == None:
887            return 'INIT_PARAM(%s, "%s")' % (name, self.desc)
888        else:
889            return 'INIT_PARAM_DFLT(%s, "%s", %s)' % \
890                   (name, self.desc, str(self.default))
891
892class _ParamProxy(object):
893    def __init__(self, type):
894        self.ptype = type
895
896    # E.g., Param.Int(5, "number of widgets")
897    def __call__(self, *args, **kwargs):
898        return _Param(self.ptype, *args, **kwargs)
899
900    def __getattr__(self, attr):
901        if attr == '__bases__':
902            raise AttributeError, ''
903        cls = type(self)
904        return cls(attr)
905
906    def __setattr__(self, attr, value):
907        if attr != 'ptype':
908            raise AttributeError, \
909                  'Attribute %s not available in %s' % (attr, self.__class__)
910        super(_ParamProxy, self).__setattr__(attr, value)
911
912
913Param = _ParamProxy(None)
914
915# Vector-valued parameter description.  Just like Param, except that
916# the value is a vector (list) of the specified type instead of a
917# single value.
918class _VectorParam(_Param):
919    def __init__(self, type, *args, **kwargs):
920        _Param.__init__(self, type, *args, **kwargs)
921
922    def valid(self, value):
923        if value == None:
924            return True
925
926        ptype = eval(self.ptype)
927        if issequence(value):
928            for val in value:
929                if not isinstance(val, Proxy):
930                    ptype._convert(val)
931        elif not isinstance(value, Proxy):
932            ptype._convert(value)
933
934    # Convert assigned value to appropriate type.  If the RHS is not a
935    # list or tuple, it generates a single-element list.
936    def convert(self, value):
937        if value == None:
938            return []
939
940        ptype = eval(self.ptype)
941        if issequence(value):
942            # list: coerce each element into new list
943            return [ ptype._convert(v) for v in value ]
944        else:
945            # singleton: coerce & wrap in a list
946            return ptype._convert(value)
947
948    def string(self, value):
949        ptype = eval(self.ptype)
950        if issequence(value):
951            return ' '.join([ ptype._string(v) for v in value])
952        else:
953            return ptype._string(value)
954
955    def sim_decl(self, name):
956        return 'VectorParam<%s> %s;' % (self.ptype.__name__, name)
957
958class _VectorParamProxy(_ParamProxy):
959    # E.g., VectorParam.Int(5, "number of widgets")
960    def __call__(self, *args, **kwargs):
961        return _VectorParam(self.ptype, *args, **kwargs)
962
963VectorParam = _VectorParamProxy(None)
964
965#####################################################################
966#
967# Parameter Types
968#
969# Though native Python types could be used to specify parameter types
970# (the 'ptype' field of the Param and VectorParam classes), it's more
971# flexible to define our own set of types.  This gives us more control
972# over how Python expressions are converted to values (via the
973# __init__() constructor) and how these values are printed out (via
974# the __str__() conversion method).  Eventually we'll need these types
975# to correspond to distinct C++ types as well.
976#
977#####################################################################
978# Integer parameter type.
979class _CheckedInt(object):
980    def _convert(cls, value):
981        t = type(value)
982        if t == bool:
983            return int(value)
984
985        if t != int and t != long and t != float and t != str:
986            raise TypeError, 'Integer parameter of invalid type %s' % t
987
988        if t == str or t == float:
989            value = long(value)
990
991        if not cls._min <= value <= cls._max:
992            raise TypeError, 'Integer parameter out of bounds %d < %d < %d' % \
993                  (cls._min, value, cls._max)
994
995        return value
996    _convert = classmethod(_convert)
997
998    def _string(cls, value):
999        return str(value)
1000    _string = classmethod(_string)
1001
1002class CheckedInt(type):
1003    def __new__(cls, name, min, max):
1004        # New class derives from _CheckedInt base with proper bounding
1005        # parameters
1006        dict = { '_name' : name, '_min' : min, '_max' : max }
1007        return type.__new__(cls, name, (_CheckedInt, ), dict)
1008
1009class CheckedIntType(CheckedInt):
1010    def __new__(cls, name, size, unsigned):
1011        dict = {}
1012        if unsigned:
1013            min = 0
1014            max = 2 ** size - 1
1015        else:
1016            min = -(2 ** (size - 1))
1017            max = (2 ** (size - 1)) - 1
1018
1019        return super(cls, CheckedIntType).__new__(cls, name, min, max)
1020
1021Int      = CheckedIntType('int',      32, False)
1022Unsigned = CheckedIntType('unsigned', 32, True)
1023
1024Int8     = CheckedIntType('int8_t',    8, False)
1025UInt8    = CheckedIntType('uint8_t',   8, True)
1026Int16    = CheckedIntType('int16_t',  16, False)
1027UInt16   = CheckedIntType('uint16_t', 16, True)
1028Int32    = CheckedIntType('int32_t',  32, False)
1029UInt32   = CheckedIntType('uint32_t', 32, True)
1030Int64    = CheckedIntType('int64_t',  64, False)
1031UInt64   = CheckedIntType('uint64_t', 64, True)
1032
1033Counter  = CheckedIntType('Counter', 64, True)
1034Addr     = CheckedIntType('Addr',    64, True)
1035Tick     = CheckedIntType('Tick',    64, True)
1036
1037Percent  = CheckedInt('int', 0, 100)
1038
1039class Pair(object):
1040    def __init__(self, first, second):
1041        self.first = first
1042        self.second = second
1043
1044class _Range(object):
1045    def _convert(cls, value):
1046        if not isinstance(value, Pair):
1047            raise TypeError, 'value %s is not a Pair' % value
1048        return Pair(cls._type._convert(value.first),
1049                    cls._type._convert(value.second))
1050    _convert = classmethod(_convert)
1051
1052    def _string(cls, value):
1053        return '%s:%s' % (cls._type._string(value.first),
1054                          cls._type._string(value.second))
1055    _string = classmethod(_string)
1056
1057def RangeSize(start, size):
1058    return Pair(start, start + size - 1)
1059
1060class Range(type):
1061    def __new__(cls, type):
1062        dict = { '_name' : 'Range<%s>' + type._name, '_type' : type }
1063        cname = 'Range_' + type.__name__
1064        return super(cls, Range).__new__(cls, cname, (_Range, ), dict)
1065
1066AddrRange = Range(Addr)
1067
1068# Boolean parameter type.
1069class Bool(object):
1070    _name = 'bool'
1071    def _convert(value):
1072        t = type(value)
1073        if t == bool:
1074            return value
1075
1076        if t == int or t == long:
1077            return bool(value)
1078
1079        if t == str:
1080            v = value.lower()
1081            if v == "true" or v == "t" or v == "yes" or v == "y":
1082                return True
1083            elif v == "false" or v == "f" or v == "no" or v == "n":
1084                return False
1085
1086        raise TypeError, 'Bool parameter (%s) of invalid type %s' % (v, t)
1087    _convert = staticmethod(_convert)
1088
1089    def _string(value):
1090        if value:
1091            return "true"
1092        else:
1093            return "false"
1094    _string = staticmethod(_string)
1095
1096# String-valued parameter.
1097class String(object):
1098    _name = 'string'
1099
1100    # Constructor.  Value must be Python string.
1101    def _convert(cls,value):
1102        if value is None:
1103            return ''
1104        if isinstance(value, str):
1105            return value
1106
1107        raise TypeError, \
1108              "String param got value %s %s" % (repr(value), type(value))
1109    _convert = classmethod(_convert)
1110
1111    # Generate printable string version.  Not too tricky.
1112    def _string(cls, value):
1113        return value
1114    _string = classmethod(_string)
1115
1116
1117def IncEthernetAddr(addr, val = 1):
1118    bytes = map(lambda x: int(x, 16), addr.split(':'))
1119    bytes[5] += val
1120    for i in (5, 4, 3, 2, 1):
1121        val,rem = divmod(bytes[i], 256)
1122        bytes[i] = rem
1123        if val == 0:
1124            break
1125        bytes[i - 1] += val
1126    assert(bytes[0] <= 255)
1127    return ':'.join(map(lambda x: '%02x' % x, bytes))
1128
1129class NextEthernetAddr(object):
1130    __metaclass__ = Singleton
1131    addr = "00:90:00:00:00:01"
1132
1133    def __init__(self, inc = 1):
1134        self.value = self.addr
1135        self.addr = IncEthernetAddr(self.addr, inc)
1136
1137class EthernetAddr(object):
1138    _name = 'EthAddr'
1139
1140    def _convert(cls, value):
1141        if value == NextEthernetAddr:
1142            return value
1143
1144        if not isinstance(value, str):
1145            raise TypeError, "expected an ethernet address and didn't get one"
1146
1147        bytes = value.split(':')
1148        if len(bytes) != 6:
1149            raise TypeError, 'invalid ethernet address %s' % value
1150
1151        for byte in bytes:
1152            if not 0 <= int(byte) <= 256:
1153                raise TypeError, 'invalid ethernet address %s' % value
1154
1155        return value
1156    _convert = classmethod(_convert)
1157
1158    def _string(cls, value):
1159        if value == NextEthernetAddr:
1160            value = value().value
1161        return value
1162    _string = classmethod(_string)
1163
1164# Special class for NULL pointers.  Note the special check in
1165# make_param_value() above that lets these be assigned where a
1166# SimObject is required.
1167# only one copy of a particular node
1168class NullSimObject(object):
1169    __metaclass__ = Singleton
1170    _name = 'NULL'
1171
1172    def __call__(cls):
1173        return cls
1174
1175    def _sim_code(cls):
1176        pass
1177    _sim_code = classmethod(_sim_code)
1178
1179    def _instantiate(self, parent = None, path = ''):
1180        pass
1181
1182    def _convert(cls, value):
1183        if value == Nxone:
1184            return
1185
1186        if isinstance(value, cls):
1187            return value
1188
1189        raise TypeError, 'object %s %s of the wrong type, should be %s' % \
1190              (repr(value), type(value), cls)
1191    _convert = classmethod(_convert)
1192
1193    def _string():
1194        return 'NULL'
1195    _string = staticmethod(_string)
1196
1197# The only instance you'll ever need...
1198Null = NULL = NullSimObject()
1199
1200# Enumerated types are a little more complex.  The user specifies the
1201# type as Enum(foo) where foo is either a list or dictionary of
1202# alternatives (typically strings, but not necessarily so).  (In the
1203# long run, the integer value of the parameter will be the list index
1204# or the corresponding dictionary value.  For now, since we only check
1205# that the alternative is valid and then spit it into a .ini file,
1206# there's not much point in using the dictionary.)
1207
1208# What Enum() must do is generate a new type encapsulating the
1209# provided list/dictionary so that specific values of the parameter
1210# can be instances of that type.  We define two hidden internal
1211# classes (_ListEnum and _DictEnum) to serve as base classes, then
1212# derive the new type from the appropriate base class on the fly.
1213
1214
1215# Base class for Enum types.
1216class _Enum(object):
1217    def _convert(self, value):
1218        if value not in self.map:
1219            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1220                  % (value, self.map)
1221        return value
1222    _convert = classmethod(_convert)
1223
1224    # Generate printable string version of value.
1225    def _string(self, value):
1226        return str(value)
1227    _string = classmethod(_string)
1228
1229# Enum metaclass... calling Enum(foo) generates a new type (class)
1230# that derives from _ListEnum or _DictEnum as appropriate.
1231class Enum(type):
1232    # counter to generate unique names for generated classes
1233    counter = 1
1234
1235    def __new__(cls, *args):
1236        if len(args) > 1:
1237            enum_map = args
1238        else:
1239            enum_map = args[0]
1240
1241        if isinstance(enum_map, dict):
1242            map = enum_map
1243        elif issequence(enum_map):
1244            map = {}
1245            for idx,val in enumerate(enum_map):
1246                map[val] = idx
1247        else:
1248            raise TypeError, "Enum map must be list or dict (got %s)" % map
1249
1250        classname = "Enum%04d" % Enum.counter
1251        Enum.counter += 1
1252
1253        # New class derives from _Enum base, and gets a 'map'
1254        # attribute containing the specified list or dict.
1255        return type.__new__(cls, classname, (_Enum, ), { 'map': map })
1256
1257
1258#
1259# "Constants"... handy aliases for various values.
1260#
1261
1262# Some memory range specifications use this as a default upper bound.
1263MAX_ADDR = Addr._max
1264MaxTick = Tick._max
1265
1266# For power-of-two sizing, e.g. 64*K gives an integer value 65536.
1267K = 1024
1268M = K*K
1269G = K*M
1270
1271#####################################################################
1272
1273# Munge an arbitrary Python code string to get it to execute (mostly
1274# dealing with indentation).  Stolen from isa_parser.py... see
1275# comments there for a more detailed description.
1276#def fixPythonIndentation(s):
1277#    # get rid of blank lines first
1278#    s = re.sub(r'(?m)^\s*\n', '', s);
1279#    if (s != '' and re.match(r'[ \t]', s[0])):
1280#        s = 'if 1:\n' + s
1281#    return s
1282
1283# Hook to generate C++ parameter code.
1284def gen_sim_code(file):
1285    for objname in sim_object_list:
1286        print >> file, eval("%s._sim_code()" % objname)
1287
1288# The final hook to generate .ini files.  Called from configuration
1289# script once config is built.
1290def instantiate(root):
1291    if not issubclass(root, Root):
1292        raise AttributeError, 'Can only instantiate the Root of the tree'
1293
1294    instance = root.instantiate('root')
1295    instance.fixup()
1296    instance.display()
1297    if not noDot:
1298       dot = pydot.Dot()
1299       instance.outputDot(dot)
1300       dot.orientation = "portrait"
1301       dot.size = "8.5,11"
1302       dot.ranksep="equally"
1303       dot.rank="samerank"
1304       dot.write("config.dot")
1305       dot.write_ps("config.ps")
1306
1307from objects import *
1308
1309