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