config.py revision 2712:aa0891b4a110
112841Sgabeblack@google.com# Copyright (c) 2004-2005 The Regents of The University of Michigan
212841Sgabeblack@google.com# All rights reserved.
312841Sgabeblack@google.com#
412841Sgabeblack@google.com# Redistribution and use in source and binary forms, with or without
512841Sgabeblack@google.com# modification, are permitted provided that the following conditions are
612841Sgabeblack@google.com# met: redistributions of source code must retain the above copyright
712841Sgabeblack@google.com# notice, this list of conditions and the following disclaimer;
812841Sgabeblack@google.com# redistributions in binary form must reproduce the above copyright
912841Sgabeblack@google.com# notice, this list of conditions and the following disclaimer in the
1012841Sgabeblack@google.com# documentation and/or other materials provided with the distribution;
1112841Sgabeblack@google.com# neither the name of the copyright holders nor the names of its
1212841Sgabeblack@google.com# contributors may be used to endorse or promote products derived from
1312841Sgabeblack@google.com# this software without specific prior written permission.
1412841Sgabeblack@google.com#
1512841Sgabeblack@google.com# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1612841Sgabeblack@google.com# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1712841Sgabeblack@google.com# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1812841Sgabeblack@google.com# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1912841Sgabeblack@google.com# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2012841Sgabeblack@google.com# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2112841Sgabeblack@google.com# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2212841Sgabeblack@google.com# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2312841Sgabeblack@google.com# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2412841Sgabeblack@google.com# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2512841Sgabeblack@google.com# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2612841Sgabeblack@google.com#
2712841Sgabeblack@google.com# Authors: Steve Reinhardt
2812841Sgabeblack@google.com#          Nathan Binkert
2912841Sgabeblack@google.com
3012841Sgabeblack@google.comimport os, re, sys, types, inspect
3112841Sgabeblack@google.com
3212841Sgabeblack@google.comimport m5
3312841Sgabeblack@google.comfrom m5 import panic
3412841Sgabeblack@google.comfrom convert import *
3513054Sgabeblack@google.comfrom multidict import multidict
3612841Sgabeblack@google.com
3713245Sgabeblack@google.comnoDot = False
3812841Sgabeblack@google.comtry:
3912841Sgabeblack@google.com    import pydot
4012841Sgabeblack@google.comexcept:
4112841Sgabeblack@google.com    noDot = True
4212841Sgabeblack@google.com
4312841Sgabeblack@google.comclass Singleton(type):
4412841Sgabeblack@google.com    def __call__(cls, *args, **kwargs):
4512841Sgabeblack@google.com        if hasattr(cls, '_instance'):
4612841Sgabeblack@google.com            return cls._instance
4712841Sgabeblack@google.com
4812841Sgabeblack@google.com        cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
4912841Sgabeblack@google.com        return cls._instance
5012841Sgabeblack@google.com
5113054Sgabeblack@google.com#####################################################################
5213054Sgabeblack@google.com#
5313054Sgabeblack@google.com# M5 Python Configuration Utility
5413054Sgabeblack@google.com#
5513054Sgabeblack@google.com# The basic idea is to write simple Python programs that build Python
5613054Sgabeblack@google.com# objects corresponding to M5 SimObjects for the desired simulation
5712841Sgabeblack@google.com# configuration.  For now, the Python emits a .ini file that can be
5812841Sgabeblack@google.com# parsed by M5.  In the future, some tighter integration between M5
5912868Sgabeblack@google.com# and the Python interpreter may allow bypassing the .ini file.
6012868Sgabeblack@google.com#
6113054Sgabeblack@google.com# Each SimObject class in M5 is represented by a Python class with the
6213054Sgabeblack@google.com# same name.  The Python inheritance tree mirrors the M5 C++ tree
6312868Sgabeblack@google.com# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
6412868Sgabeblack@google.com# SimObjects inherit from a single SimObject base class).  To specify
6513054Sgabeblack@google.com# an instance of an M5 SimObject in a configuration, the user simply
6613054Sgabeblack@google.com# instantiates the corresponding Python object.  The parameters for
6712868Sgabeblack@google.com# that SimObject are given by assigning to attributes of the Python
6812868Sgabeblack@google.com# object, either using keyword assignment in the constructor or in
6913054Sgabeblack@google.com# separate assignment statements.  For example:
7013054Sgabeblack@google.com#
7112868Sgabeblack@google.com# cache = BaseCache(size='64KB')
7212868Sgabeblack@google.com# cache.hit_latency = 3
7313054Sgabeblack@google.com# cache.assoc = 8
7413054Sgabeblack@google.com#
7512868Sgabeblack@google.com# The magic lies in the mapping of the Python attributes for SimObject
7612868Sgabeblack@google.com# classes to the actual SimObject parameter specifications.  This
7713054Sgabeblack@google.com# allows parameter validity checking in the Python code.  Continuing
7813054Sgabeblack@google.com# the example above, the statements "cache.blurfl=3" or
7912868Sgabeblack@google.com# "cache.assoc='hello'" would both result in runtime errors in Python,
8012868Sgabeblack@google.com# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
8113054Sgabeblack@google.com# parameter requires an integer, respectively.  This magic is done
8213054Sgabeblack@google.com# primarily by overriding the special __setattr__ method that controls
8312868Sgabeblack@google.com# assignment to object attributes.
8412868Sgabeblack@google.com#
8512841Sgabeblack@google.com# Once a set of Python objects have been instantiated in a hierarchy,
8613054Sgabeblack@google.com# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
8712841Sgabeblack@google.com# will generate a .ini file.  See simple-4cpu.py for an example
8813054Sgabeblack@google.com# (corresponding to m5-test/simple-4cpu.ini).
8913054Sgabeblack@google.com#
9013054Sgabeblack@google.com#####################################################################
9113054Sgabeblack@google.com
9213054Sgabeblack@google.com#####################################################################
9313054Sgabeblack@google.com#
9413054Sgabeblack@google.com# ConfigNode/SimObject classes
9513054Sgabeblack@google.com#
9613054Sgabeblack@google.com# The Python class hierarchy rooted by ConfigNode (which is the base
9712841Sgabeblack@google.com# class of SimObject, which in turn is the base class of all other M5
9812841Sgabeblack@google.com# SimObject classes) has special attribute behavior.  In general, an
9913054Sgabeblack@google.com# object in this hierarchy has three categories of attribute-like
10012841Sgabeblack@google.com# things:
10113054Sgabeblack@google.com#
10212841Sgabeblack@google.com# 1. Regular Python methods and variables.  These must start with an
10312841Sgabeblack@google.com# underscore to be treated normally.
10412841Sgabeblack@google.com#
10513054Sgabeblack@google.com# 2. SimObject parameters.  These values are stored as normal Python
10612841Sgabeblack@google.com# attributes, but all assignments to these attributes are checked
10713054Sgabeblack@google.com# against the pre-defined set of parameters stored in the class's
10812841Sgabeblack@google.com# _params dictionary.  Assignments to attributes that do not
10912841Sgabeblack@google.com# correspond to predefined parameters, or that are not of the correct
11013054Sgabeblack@google.com# type, incur runtime errors.
11112841Sgabeblack@google.com#
11213054Sgabeblack@google.com# 3. Hierarchy children.  The child nodes of a ConfigNode are stored
11312841Sgabeblack@google.com# in the node's _children dictionary, but can be accessed using the
11412841Sgabeblack@google.com# Python attribute dot-notation (just as they are printed out by the
11513245Sgabeblack@google.com# simulator).  Children cannot be created using attribute assigment;
11613245Sgabeblack@google.com# they must be added by specifying the parent node in the child's
11713245Sgabeblack@google.com# constructor or using the '+=' operator.
11813245Sgabeblack@google.com
11913245Sgabeblack@google.com# The SimObject parameters are the most complex, for a few reasons.
12013245Sgabeblack@google.com# First, both parameter descriptions and parameter values are
12113245Sgabeblack@google.com# inherited.  Thus parameter description lookup must go up the
12213245Sgabeblack@google.com# inheritance chain like normal attribute lookup, but this behavior
12312841Sgabeblack@google.com# must be explicitly coded since the lookup occurs in each class's
12413054Sgabeblack@google.com# _params attribute.  Second, because parameter values can be set
12513054Sgabeblack@google.com# on SimObject classes (to implement default values), the parameter
12612841Sgabeblack@google.com# checking behavior must be enforced on class attribute assignments as
12713054Sgabeblack@google.com# well as instance attribute assignments.  Finally, because we allow
12812841Sgabeblack@google.com# class specialization via inheritance (e.g., see the L1Cache class in
12912841Sgabeblack@google.com# the simple-4cpu.py example), we must do parameter checking even on
13012841Sgabeblack@google.com# class instantiation.  To provide all these features, we use a
13113054Sgabeblack@google.com# metaclass to define most of the SimObject parameter behavior for
13212841Sgabeblack@google.com# this class hierarchy.
13313054Sgabeblack@google.com#
13413054Sgabeblack@google.com#####################################################################
13512841Sgabeblack@google.com
13612841Sgabeblack@google.comdef isSimObject(value):
13712841Sgabeblack@google.com    return isinstance(value, SimObject)
13813245Sgabeblack@google.com
13913245Sgabeblack@google.comdef isSimObjectClass(value):
14013245Sgabeblack@google.com    try:
14113245Sgabeblack@google.com        return issubclass(value, SimObject)
14213245Sgabeblack@google.com    except TypeError:
14313245Sgabeblack@google.com        # happens if value is not a class at all
14412841Sgabeblack@google.com        return False
14513054Sgabeblack@google.com
14613054Sgabeblack@google.comdef isSimObjSequence(value):
14713245Sgabeblack@google.com    if not isinstance(value, (list, tuple)):
14813245Sgabeblack@google.com        return False
14912841Sgabeblack@google.com
15013054Sgabeblack@google.com    for val in value:
15113054Sgabeblack@google.com        if not isNullPointer(val) and not isSimObject(val):
15212841Sgabeblack@google.com            return False
15312841Sgabeblack@google.com
15412841Sgabeblack@google.com    return True
15512841Sgabeblack@google.com
15613245Sgabeblack@google.comdef isSimObjClassSequence(value):
15712841Sgabeblack@google.com    if not isinstance(value, (list, tuple)):
15813245Sgabeblack@google.com        return False
15913245Sgabeblack@google.com
16013245Sgabeblack@google.com    for val in value:
16113245Sgabeblack@google.com        if not isNullPointer(val) and not isSimObjectClass(val):
16212841Sgabeblack@google.com            return False
16312841Sgabeblack@google.com
16412841Sgabeblack@google.com    return True
16512841Sgabeblack@google.com
16612841Sgabeblack@google.comdef isNullPointer(value):
16712841Sgabeblack@google.com    return isinstance(value, NullSimObject)
16813054Sgabeblack@google.com
16913054Sgabeblack@google.com# The metaclass for ConfigNode (and thus for everything that derives
17013054Sgabeblack@google.com# from ConfigNode, including SimObject).  This class controls how new
17113054Sgabeblack@google.com# classes that derive from ConfigNode are instantiated, and provides
17213054Sgabeblack@google.com# inherited class behavior (just like a class controls how instances
17313054Sgabeblack@google.com# of that class are instantiated, and provides inherited instance
17412841Sgabeblack@google.com# behavior).
17513054Sgabeblack@google.comclass MetaSimObject(type):
17613054Sgabeblack@google.com    # Attributes that can be set only at initialization time
17713054Sgabeblack@google.com    init_keywords = { 'abstract' : types.BooleanType,
17813054Sgabeblack@google.com                      'type' : types.StringType }
17913054Sgabeblack@google.com    # Attributes that can be set any time
18013054Sgabeblack@google.com    keywords = { 'check' : types.FunctionType,
18112841Sgabeblack@google.com                 'children' : types.ListType }
18212841Sgabeblack@google.com
18312868Sgabeblack@google.com    # __new__ is called before __init__, and is where the statements
18412868Sgabeblack@google.com    # in the body of the class definition get loaded into the class's
18513054Sgabeblack@google.com    # __dict__.  We intercept this to filter out parameter assignments
18613054Sgabeblack@google.com    # and only allow "private" attributes to be passed to the base
18713054Sgabeblack@google.com    # __new__ (starting with underscore).
18813054Sgabeblack@google.com    def __new__(mcls, name, bases, dict):
18913054Sgabeblack@google.com        if dict.has_key('_init_dict'):
19012868Sgabeblack@google.com            # must have been called from makeSubclass() rather than
19112868Sgabeblack@google.com            # via Python class declaration; bypass filtering process.
19213054Sgabeblack@google.com            cls_dict = dict
19313054Sgabeblack@google.com        else:
19413054Sgabeblack@google.com            # Copy "private" attributes (including special methods
19513054Sgabeblack@google.com            # such as __new__) to the official dict.  Everything else
19613054Sgabeblack@google.com            # goes in _init_dict to be filtered in __init__.
19712868Sgabeblack@google.com            cls_dict = {}
19812868Sgabeblack@google.com            for key,val in dict.items():
19913054Sgabeblack@google.com                if key.startswith('_'):
20013054Sgabeblack@google.com                    cls_dict[key] = val
20113054Sgabeblack@google.com                    del dict[key]
20213054Sgabeblack@google.com            cls_dict['_init_dict'] = dict
20313054Sgabeblack@google.com        return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
20412868Sgabeblack@google.com
20512868Sgabeblack@google.com    # subclass initialization
20613054Sgabeblack@google.com    def __init__(cls, name, bases, dict):
20713054Sgabeblack@google.com        # calls type.__init__()... I think that's a no-op, but leave
20813054Sgabeblack@google.com        # it here just in case it's not.
20913054Sgabeblack@google.com        super(MetaSimObject, cls).__init__(name, bases, dict)
21013054Sgabeblack@google.com
21112868Sgabeblack@google.com        # initialize required attributes
21212868Sgabeblack@google.com        cls._params = multidict()
21313054Sgabeblack@google.com        cls._values = multidict()
21413054Sgabeblack@google.com        cls._instantiated = False # really instantiated or subclassed
21513054Sgabeblack@google.com        cls._anon_subclass_counter = 0
21613054Sgabeblack@google.com
21713054Sgabeblack@google.com        # We don't support multiple inheritance.  If you want to, you
21812868Sgabeblack@google.com        # must fix multidict to deal with it properly.
21912868Sgabeblack@google.com        if len(bases) > 1:
22013054Sgabeblack@google.com            raise TypeError, "SimObjects do not support multiple inheritance"
22113054Sgabeblack@google.com
22213054Sgabeblack@google.com        base = bases[0]
22313054Sgabeblack@google.com
22413054Sgabeblack@google.com        # the only time the following is not true is when we define
22512868Sgabeblack@google.com        # the SimObject class itself
22612868Sgabeblack@google.com        if isinstance(base, MetaSimObject):
22712841Sgabeblack@google.com            cls._params.parent = base._params
22813054Sgabeblack@google.com            cls._values.parent = base._values
22912841Sgabeblack@google.com            base._instantiated = True
23013054Sgabeblack@google.com
23113054Sgabeblack@google.com        # now process the _init_dict items
23213054Sgabeblack@google.com        for key,val in cls._init_dict.items():
23313054Sgabeblack@google.com            if isinstance(val, (types.FunctionType, types.TypeType)):
23413054Sgabeblack@google.com                type.__setattr__(cls, key, val)
23513054Sgabeblack@google.com
23613054Sgabeblack@google.com            # param descriptions
23713054Sgabeblack@google.com            elif isinstance(val, ParamDesc):
23813054Sgabeblack@google.com                cls._new_param(key, val)
23912841Sgabeblack@google.com
24012841Sgabeblack@google.com            # init-time-only keywords
24113054Sgabeblack@google.com            elif cls.init_keywords.has_key(key):
24212841Sgabeblack@google.com                cls._set_keyword(key, val, cls.init_keywords[key])
24313054Sgabeblack@google.com
24412841Sgabeblack@google.com            # default: use normal path (ends up in __setattr__)
24512841Sgabeblack@google.com            else:
24612841Sgabeblack@google.com                setattr(cls, key, val)
24713054Sgabeblack@google.com
24812841Sgabeblack@google.com        # Pull the deep-copy memoization dict out of the class dict if
24913054Sgabeblack@google.com        # it's there...
25012841Sgabeblack@google.com        memo = cls.__dict__.get('_memo', {})
25112841Sgabeblack@google.com
25213054Sgabeblack@google.com        # Handle SimObject values
25312841Sgabeblack@google.com        for key,val in cls._values.iteritems():
25413054Sgabeblack@google.com            # SimObject instances need to be promoted to classes.
25512841Sgabeblack@google.com            # Existing classes should not have any instance values, so
25612841Sgabeblack@google.com            # these can only occur at the lowest level dict (the
25713245Sgabeblack@google.com            # parameters just being set in this class definition).
25813245Sgabeblack@google.com            if isSimObject(val):
25913245Sgabeblack@google.com                assert(val == cls._values.local[key])
26013245Sgabeblack@google.com                cls._values[key] = val.makeClass(memo)
26113245Sgabeblack@google.com            elif isSimObjSequence(val) and len(val):
26213245Sgabeblack@google.com                assert(val == cls._values.local[key])
26313245Sgabeblack@google.com                cls._values[key] = [ v.makeClass(memo) for v in val ]
26413245Sgabeblack@google.com            # SimObject classes need to be subclassed so that
26512841Sgabeblack@google.com            # parameters that get set at this level only affect this
26613054Sgabeblack@google.com            # level and derivatives.
26713054Sgabeblack@google.com            elif isSimObjectClass(val):
26812841Sgabeblack@google.com                assert(not cls._values.local.has_key(key))
26913054Sgabeblack@google.com                cls._values[key] = val.makeSubclass({}, memo)
27012841Sgabeblack@google.com            elif isSimObjClassSequence(val) and len(val):
27112841Sgabeblack@google.com                assert(not cls._values.local.has_key(key))
27212841Sgabeblack@google.com                cls._values[key] = [ v.makeSubclass({}, memo) for v in val ]
27313054Sgabeblack@google.com
27412841Sgabeblack@google.com
27512841Sgabeblack@google.com    def _set_keyword(cls, keyword, val, kwtype):
27612841Sgabeblack@google.com        if not isinstance(val, kwtype):
27712841Sgabeblack@google.com            raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
27813054Sgabeblack@google.com                  (keyword, type(val), kwtype)
27912841Sgabeblack@google.com        if isinstance(val, types.FunctionType):
28012841Sgabeblack@google.com            val = classmethod(val)
28112841Sgabeblack@google.com        type.__setattr__(cls, keyword, val)
28212841Sgabeblack@google.com
28313054Sgabeblack@google.com    def _new_param(cls, name, value):
28412841Sgabeblack@google.com        cls._params[name] = value
28512841Sgabeblack@google.com        if hasattr(value, 'default'):
28613054Sgabeblack@google.com            setattr(cls, name, value.default)
28713054Sgabeblack@google.com
28813054Sgabeblack@google.com    # Set attribute (called on foo.attr = value when foo is an
28912841Sgabeblack@google.com    # instance of class cls).
29013054Sgabeblack@google.com    def __setattr__(cls, attr, value):
29113054Sgabeblack@google.com        # normal processing for private attributes
29213054Sgabeblack@google.com        if attr.startswith('_'):
29312841Sgabeblack@google.com            type.__setattr__(cls, attr, value)
29412841Sgabeblack@google.com            return
29512841Sgabeblack@google.com
29613245Sgabeblack@google.com        if cls.keywords.has_key(attr):
29713245Sgabeblack@google.com            cls._set_keyword(attr, value, cls.keywords[attr])
29813245Sgabeblack@google.com            return
29913245Sgabeblack@google.com
30013245Sgabeblack@google.com        # must be SimObject param
30113245Sgabeblack@google.com        param = cls._params.get(attr, None)
30212841Sgabeblack@google.com        if param:
30313054Sgabeblack@google.com            # It's ok: set attribute by delegating to 'object' class.
30413054Sgabeblack@google.com            if (isSimObject(value) or isSimObjSequence(value)) \
30513054Sgabeblack@google.com                   and cls._instantiated:
30613054Sgabeblack@google.com                raise AttributeError, \
30713245Sgabeblack@google.com                  "Cannot set SimObject parameter '%s' after\n" \
30813245Sgabeblack@google.com                  "    class %s has been instantiated or subclassed" \
30912841Sgabeblack@google.com                  % (attr, cls.__name__)
31013054Sgabeblack@google.com            try:
31113054Sgabeblack@google.com                cls._values[attr] = param.convert(value)
31212841Sgabeblack@google.com            except Exception, e:
31312841Sgabeblack@google.com                msg = "%s\nError setting param %s.%s to %s\n" % \
31412841Sgabeblack@google.com                      (e, cls.__name__, attr, value)
31512841Sgabeblack@google.com                e.args = (msg, )
31613245Sgabeblack@google.com                raise
31713245Sgabeblack@google.com        # I would love to get rid of this
31812841Sgabeblack@google.com        elif isSimObject(value) or isSimObjSequence(value):
31913245Sgabeblack@google.com           cls._values[attr] = value
32013245Sgabeblack@google.com        else:
32113245Sgabeblack@google.com            raise AttributeError, \
32213245Sgabeblack@google.com                  "Class %s has no parameter %s" % (cls.__name__, attr)
32312841Sgabeblack@google.com
32412841Sgabeblack@google.com    def __getattr__(cls, attr):
32512841Sgabeblack@google.com        if cls._values.has_key(attr):
32612841Sgabeblack@google.com            return cls._values[attr]
32712841Sgabeblack@google.com
32812841Sgabeblack@google.com        raise AttributeError, \
32912841Sgabeblack@google.com              "object '%s' has no attribute '%s'" % (cls.__name__, attr)
33013054Sgabeblack@google.com
33113054Sgabeblack@google.com    # Create a subclass of this class.  Basically a function interface
33213054Sgabeblack@google.com    # to the standard Python class definition mechanism, primarily for
33313054Sgabeblack@google.com    # internal use.  'memo' dict param supports "deep copy" (really
33413054Sgabeblack@google.com    # "deep subclass") operations... within a given operation,
33513054Sgabeblack@google.com    # multiple references to a class should result in a single
33612841Sgabeblack@google.com    # subclass object with multiple references to it (as opposed to
33713054Sgabeblack@google.com    # mutiple unique subclasses).
33813054Sgabeblack@google.com    def makeSubclass(cls, init_dict, memo = {}):
33913054Sgabeblack@google.com        subcls = memo.get(cls)
34013054Sgabeblack@google.com        if not subcls:
34113054Sgabeblack@google.com            name = cls.__name__ + '_' + str(cls._anon_subclass_counter)
34212841Sgabeblack@google.com            cls._anon_subclass_counter += 1
34312841Sgabeblack@google.com            subcls = MetaSimObject(name, (cls,),
34412841Sgabeblack@google.com                                   { '_init_dict': init_dict, '_memo': memo })
34512868Sgabeblack@google.com        return subcls
34612868Sgabeblack@google.com
34713054Sgabeblack@google.com# The ConfigNode class is the root of the special hierarchy.  Most of
34813054Sgabeblack@google.com# the code in this class deals with the configuration hierarchy itself
34913054Sgabeblack@google.com# (parent/child node relationships).
35013054Sgabeblack@google.comclass SimObject(object):
35113054Sgabeblack@google.com    # Specify metaclass.  Any class inheriting from SimObject will
35212868Sgabeblack@google.com    # get this metaclass.
35312868Sgabeblack@google.com    __metaclass__ = MetaSimObject
35412868Sgabeblack@google.com
35513054Sgabeblack@google.com    # __new__ operator allocates new instances of the class.  We
35613054Sgabeblack@google.com    # override it here just to support "deep instantiation" operation
35713054Sgabeblack@google.com    # via the _memo dict.  When recursively instantiating an object
35813054Sgabeblack@google.com    # hierarchy we want to make sure that each class is instantiated
35913054Sgabeblack@google.com    # only once, and that if there are multiple references to the same
36012868Sgabeblack@google.com    # original class, we end up with the corresponding instantiated
36112868Sgabeblack@google.com    # references all pointing to the same instance.
36213054Sgabeblack@google.com    def __new__(cls, _memo = None, **kwargs):
36313054Sgabeblack@google.com        if _memo is not None and _memo.has_key(cls):
36413054Sgabeblack@google.com            # return previously instantiated object
36513054Sgabeblack@google.com            assert(len(kwargs) == 0)
36613054Sgabeblack@google.com            return _memo[cls]
36712868Sgabeblack@google.com        else:
36812868Sgabeblack@google.com            # Need a new one... if it needs to be memoized, this will
36912868Sgabeblack@google.com            # happen in __init__.  We defer the insertion until then
37013054Sgabeblack@google.com            # so __init__ can use the memo dict to tell whether or not
37113054Sgabeblack@google.com            # to perform the initialization.
37213054Sgabeblack@google.com            return super(SimObject, cls).__new__(cls, **kwargs)
37313054Sgabeblack@google.com
37413054Sgabeblack@google.com    # Initialize new instance previously allocated by __new__.  For
37512868Sgabeblack@google.com    # objects with SimObject-valued params, we need to recursively
37612868Sgabeblack@google.com    # instantiate the classes represented by those param values as
37713054Sgabeblack@google.com    # well (in a consistent "deep copy"-style fashion; see comment
37813054Sgabeblack@google.com    # above).
37913054Sgabeblack@google.com    def __init__(self, _memo = None, **kwargs):
38013054Sgabeblack@google.com        if _memo is not None:
38113054Sgabeblack@google.com            # We're inside a "deep instantiation"
38212868Sgabeblack@google.com            assert(isinstance(_memo, dict))
38312868Sgabeblack@google.com            assert(len(kwargs) == 0)
38412868Sgabeblack@google.com            if _memo.has_key(self.__class__):
38513054Sgabeblack@google.com                # __new__ returned an existing, already initialized
38613054Sgabeblack@google.com                # instance, so there's nothing to do here
38713054Sgabeblack@google.com                assert(_memo[self.__class__] == self)
38813054Sgabeblack@google.com                return
38913054Sgabeblack@google.com            # no pre-existing object, so remember this one here
39012868Sgabeblack@google.com            _memo[self.__class__] = self
39112868Sgabeblack@google.com        else:
39212841Sgabeblack@google.com            # This is a new top-level instantiation... don't memoize
39313054Sgabeblack@google.com            # this objcet, but prepare to memoize any recursively
39412841Sgabeblack@google.com            # instantiated objects.
39513054Sgabeblack@google.com            _memo = {}
39613054Sgabeblack@google.com
39712841Sgabeblack@google.com        self.__class__._instantiated = True
39812841Sgabeblack@google.com
39913054Sgabeblack@google.com        self._children = {}
40013054Sgabeblack@google.com        # Inherit parameter values from class using multidict so
40113054Sgabeblack@google.com        # individual value settings can be overridden.
40213054Sgabeblack@google.com        self._values = multidict(self.__class__._values)
40312841Sgabeblack@google.com        # For SimObject-valued parameters, the class should have
40413054Sgabeblack@google.com        # classes (not instances) for the values.  We need to
40513054Sgabeblack@google.com        # instantiate these classes rather than just inheriting the
40613054Sgabeblack@google.com        # class object.
40713054Sgabeblack@google.com        for key,val in self.__class__._values.iteritems():
40813054Sgabeblack@google.com            if isSimObjectClass(val):
40913054Sgabeblack@google.com                setattr(self, key, val(_memo))
41012841Sgabeblack@google.com            elif isSimObjClassSequence(val) and len(val):
41112841Sgabeblack@google.com                setattr(self, key, [ v(_memo) for v in val ])
41212841Sgabeblack@google.com        # apply attribute assignments from keyword args, if any
41313054Sgabeblack@google.com        for key,val in kwargs.iteritems():
41412841Sgabeblack@google.com            setattr(self, key, val)
41513054Sgabeblack@google.com
41612841Sgabeblack@google.com    # Use this instance as a template to create a new class.
41712841Sgabeblack@google.com    def makeClass(self, memo = {}):
41813054Sgabeblack@google.com        cls = memo.get(self)
41912841Sgabeblack@google.com        if not cls:
42013054Sgabeblack@google.com            cls =  self.__class__.makeSubclass(self._values.local)
42112841Sgabeblack@google.com            memo[self] = cls
42212841Sgabeblack@google.com        return cls
42313245Sgabeblack@google.com
42413245Sgabeblack@google.com    # Direct instantiation of instances (cloning) is no longer
42513245Sgabeblack@google.com    # allowed; must generate class from instance first.
42613245Sgabeblack@google.com    def __call__(self, **kwargs):
42713245Sgabeblack@google.com        raise TypeError, "cannot instantiate SimObject; "\
42813245Sgabeblack@google.com              "use makeClass() to make class first"
42913245Sgabeblack@google.com
43013245Sgabeblack@google.com    def __getattr__(self, attr):
43112841Sgabeblack@google.com        if self._values.has_key(attr):
43213054Sgabeblack@google.com            return self._values[attr]
43313054Sgabeblack@google.com
43412841Sgabeblack@google.com        raise AttributeError, "object '%s' has no attribute '%s'" \
43513054Sgabeblack@google.com              % (self.__class__.__name__, attr)
43612841Sgabeblack@google.com
43712841Sgabeblack@google.com    # Set attribute (called on foo.attr = value when foo is an
43812841Sgabeblack@google.com    # instance of class cls).
43913054Sgabeblack@google.com    def __setattr__(self, attr, value):
44012841Sgabeblack@google.com        # normal processing for private attributes
44113054Sgabeblack@google.com        if attr.startswith('_'):
44213054Sgabeblack@google.com            object.__setattr__(self, attr, value)
44312841Sgabeblack@google.com            return
44413054Sgabeblack@google.com
44513054Sgabeblack@google.com        # must be SimObject param
44613054Sgabeblack@google.com        param = self._params.get(attr, None)
44712841Sgabeblack@google.com        if param:
44813054Sgabeblack@google.com            # It's ok: set attribute by delegating to 'object' class.
44913054Sgabeblack@google.com            try:
45013054Sgabeblack@google.com                value = param.convert(value)
45112841Sgabeblack@google.com            except Exception, e:
45212841Sgabeblack@google.com                msg = "%s\nError setting param %s.%s to %s\n" % \
45312841Sgabeblack@google.com                      (e, self.__class__.__name__, attr, value)
45413245Sgabeblack@google.com                e.args = (msg, )
45513245Sgabeblack@google.com                raise
45613245Sgabeblack@google.com        # I would love to get rid of this
45713245Sgabeblack@google.com        elif isSimObject(value) or isSimObjSequence(value):
45813245Sgabeblack@google.com            pass
45913245Sgabeblack@google.com        else:
46012841Sgabeblack@google.com            raise AttributeError, "Class %s has no parameter %s" \
46113054Sgabeblack@google.com                  % (self.__class__.__name__, attr)
46213054Sgabeblack@google.com
46313054Sgabeblack@google.com        # clear out old child with this name, if any
46413054Sgabeblack@google.com        self.clear_child(attr)
46513054Sgabeblack@google.com
46613245Sgabeblack@google.com        if isSimObject(value):
46713245Sgabeblack@google.com            value.set_path(self, attr)
46812841Sgabeblack@google.com        elif isSimObjSequence(value):
46913054Sgabeblack@google.com            value = SimObjVector(value)
47013054Sgabeblack@google.com            [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)]
47112841Sgabeblack@google.com
47212841Sgabeblack@google.com        self._values[attr] = value
47312841Sgabeblack@google.com
47412841Sgabeblack@google.com    # this hack allows tacking a '[0]' onto parameters that may or may
47513245Sgabeblack@google.com    # not be vectors, and always getting the first element (e.g. cpus)
47613245Sgabeblack@google.com    def __getitem__(self, key):
47712841Sgabeblack@google.com        if key == 0:
47813245Sgabeblack@google.com            return self
47913245Sgabeblack@google.com        raise TypeError, "Non-zero index '%s' to SimObject" % key
48013245Sgabeblack@google.com
48113245Sgabeblack@google.com    # clear out children with given name, even if it's a vector
48212841Sgabeblack@google.com    def clear_child(self, name):
48312841Sgabeblack@google.com        if not self._children.has_key(name):
48412841Sgabeblack@google.com            return
48512841Sgabeblack@google.com        child = self._children[name]
48612841Sgabeblack@google.com        if isinstance(child, SimObjVector):
487            for i in xrange(len(child)):
488                del self._children["s%d" % (name, i)]
489        del self._children[name]
490
491    def add_child(self, name, value):
492        self._children[name] = value
493
494    def set_path(self, parent, name):
495        if not hasattr(self, '_parent'):
496            self._parent = parent
497            self._name = name
498            parent.add_child(name, self)
499
500    def path(self):
501        if not hasattr(self, '_parent'):
502            return 'root'
503        ppath = self._parent.path()
504        if ppath == 'root':
505            return self._name
506        return ppath + "." + self._name
507
508    def __str__(self):
509        return self.path()
510
511    def ini_str(self):
512        return self.path()
513
514    def find_any(self, ptype):
515        if isinstance(self, ptype):
516            return self, True
517
518        found_obj = None
519        for child in self._children.itervalues():
520            if isinstance(child, ptype):
521                if found_obj != None and child != found_obj:
522                    raise AttributeError, \
523                          'parent.any matched more than one: %s %s' % \
524                          (found_obj.path, child.path)
525                found_obj = child
526        # search param space
527        for pname,pdesc in self._params.iteritems():
528            if issubclass(pdesc.ptype, ptype):
529                match_obj = self._values[pname]
530                if found_obj != None and found_obj != match_obj:
531                    raise AttributeError, \
532                          'parent.any matched more than one: %s' % obj.path
533                found_obj = match_obj
534        return found_obj, found_obj != None
535
536    def unproxy(self, base):
537        return self
538
539    def print_ini(self):
540        print '[' + self.path() + ']'	# .ini section header
541
542        if hasattr(self, 'type') and not isinstance(self, ParamContext):
543            print 'type=%s' % self.type
544
545        child_names = self._children.keys()
546        child_names.sort()
547        np_child_names = [c for c in child_names \
548                          if not isinstance(self._children[c], ParamContext)]
549        if len(np_child_names):
550            print 'children=%s' % ' '.join(np_child_names)
551
552        param_names = self._params.keys()
553        param_names.sort()
554        for param in param_names:
555            value = self._values.get(param, None)
556            if value != None:
557                if isproxy(value):
558                    try:
559                        value = value.unproxy(self)
560                    except:
561                        print >> sys.stderr, \
562                              "Error in unproxying param '%s' of %s" % \
563                              (param, self.path())
564                        raise
565                    setattr(self, param, value)
566                print '%s=%s' % (param, self._values[param].ini_str())
567
568        print	# blank line between objects
569
570        for child in child_names:
571            self._children[child].print_ini()
572
573    # generate output file for 'dot' to display as a pretty graph.
574    # this code is currently broken.
575    def outputDot(self, dot):
576        label = "{%s|" % self.path
577        if isSimObject(self.realtype):
578            label +=  '%s|' % self.type
579
580        if self.children:
581            # instantiate children in same order they were added for
582            # backward compatibility (else we can end up with cpu1
583            # before cpu0).
584            for c in self.children:
585                dot.add_edge(pydot.Edge(self.path,c.path, style="bold"))
586
587        simobjs = []
588        for param in self.params:
589            try:
590                if param.value is None:
591                    raise AttributeError, 'Parameter with no value'
592
593                value = param.value
594                string = param.string(value)
595            except Exception, e:
596                msg = 'exception in %s:%s\n%s' % (self.name, param.name, e)
597                e.args = (msg, )
598                raise
599
600            if isSimObject(param.ptype) and string != "Null":
601                simobjs.append(string)
602            else:
603                label += '%s = %s\\n' % (param.name, string)
604
605        for so in simobjs:
606            label += "|<%s> %s" % (so, so)
607            dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so,
608                                    tailport="w"))
609        label += '}'
610        dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label))
611
612        # recursively dump out children
613        for c in self.children:
614            c.outputDot(dot)
615
616class ParamContext(SimObject):
617    pass
618
619#####################################################################
620#
621# Proxy object support.
622#
623#####################################################################
624
625class BaseProxy(object):
626    def __init__(self, search_self, search_up):
627        self._search_self = search_self
628        self._search_up = search_up
629        self._multiplier = None
630
631    def __setattr__(self, attr, value):
632        if not attr.startswith('_'):
633            raise AttributeError, 'cannot set attribute on proxy object'
634        super(BaseProxy, self).__setattr__(attr, value)
635
636    # support multiplying proxies by constants
637    def __mul__(self, other):
638        if not isinstance(other, (int, long, float)):
639            raise TypeError, "Proxy multiplier must be integer"
640        if self._multiplier == None:
641            self._multiplier = other
642        else:
643            # support chained multipliers
644            self._multiplier *= other
645        return self
646
647    __rmul__ = __mul__
648
649    def _mulcheck(self, result):
650        if self._multiplier == None:
651            return result
652        return result * self._multiplier
653
654    def unproxy(self, base):
655        obj = base
656        done = False
657
658        if self._search_self:
659            result, done = self.find(obj)
660
661        if self._search_up:
662            while not done:
663                try: obj = obj._parent
664                except: break
665
666                result, done = self.find(obj)
667
668        if not done:
669            raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \
670                  (self.path(), base.path())
671
672        if isinstance(result, BaseProxy):
673            if result == self:
674                raise RuntimeError, "Cycle in unproxy"
675            result = result.unproxy(obj)
676
677        return self._mulcheck(result)
678
679    def getindex(obj, index):
680        if index == None:
681            return obj
682        try:
683            obj = obj[index]
684        except TypeError:
685            if index != 0:
686                raise
687            # if index is 0 and item is not subscriptable, just
688            # use item itself (so cpu[0] works on uniprocessors)
689        return obj
690    getindex = staticmethod(getindex)
691
692    def set_param_desc(self, pdesc):
693        self._pdesc = pdesc
694
695class AttrProxy(BaseProxy):
696    def __init__(self, search_self, search_up, attr):
697        super(AttrProxy, self).__init__(search_self, search_up)
698        self._attr = attr
699        self._modifiers = []
700
701    def __getattr__(self, attr):
702        # python uses __bases__ internally for inheritance
703        if attr.startswith('_'):
704            return super(AttrProxy, self).__getattr__(self, attr)
705        if hasattr(self, '_pdesc'):
706            raise AttributeError, "Attribute reference on bound proxy"
707        self._modifiers.append(attr)
708        return self
709
710    # support indexing on proxies (e.g., Self.cpu[0])
711    def __getitem__(self, key):
712        if not isinstance(key, int):
713            raise TypeError, "Proxy object requires integer index"
714        self._modifiers.append(key)
715        return self
716
717    def find(self, obj):
718        try:
719            val = getattr(obj, self._attr)
720        except:
721            return None, False
722        while isproxy(val):
723            val = val.unproxy(obj)
724        for m in self._modifiers:
725            if isinstance(m, str):
726                val = getattr(val, m)
727            elif isinstance(m, int):
728                val = val[m]
729            else:
730                assert("Item must be string or integer")
731            while isproxy(val):
732                val = val.unproxy(obj)
733        return val, True
734
735    def path(self):
736        p = self._attr
737        for m in self._modifiers:
738            if isinstance(m, str):
739                p += '.%s' % m
740            elif isinstance(m, int):
741                p += '[%d]' % m
742            else:
743                assert("Item must be string or integer")
744        return p
745
746class AnyProxy(BaseProxy):
747    def find(self, obj):
748        return obj.find_any(self._pdesc.ptype)
749
750    def path(self):
751        return 'any'
752
753def isproxy(obj):
754    if isinstance(obj, (BaseProxy, EthernetAddr)):
755        return True
756    elif isinstance(obj, (list, tuple)):
757        for v in obj:
758            if isproxy(v):
759                return True
760    return False
761
762class ProxyFactory(object):
763    def __init__(self, search_self, search_up):
764        self.search_self = search_self
765        self.search_up = search_up
766
767    def __getattr__(self, attr):
768        if attr == 'any':
769            return AnyProxy(self.search_self, self.search_up)
770        else:
771            return AttrProxy(self.search_self, self.search_up, attr)
772
773# global objects for handling proxies
774Parent = ProxyFactory(search_self = False, search_up = True)
775Self = ProxyFactory(search_self = True, search_up = False)
776
777#####################################################################
778#
779# Parameter description classes
780#
781# The _params dictionary in each class maps parameter names to
782# either a Param or a VectorParam object.  These objects contain the
783# parameter description string, the parameter type, and the default
784# value (loaded from the PARAM section of the .odesc files).  The
785# _convert() method on these objects is used to force whatever value
786# is assigned to the parameter to the appropriate type.
787#
788# Note that the default values are loaded into the class's attribute
789# space when the parameter dictionary is initialized (in
790# MetaConfigNode._setparams()); after that point they aren't used.
791#
792#####################################################################
793
794# Dummy base class to identify types that are legitimate for SimObject
795# parameters.
796class ParamValue(object):
797
798    # default for printing to .ini file is regular string conversion.
799    # will be overridden in some cases
800    def ini_str(self):
801        return str(self)
802
803    # allows us to blithely call unproxy() on things without checking
804    # if they're really proxies or not
805    def unproxy(self, base):
806        return self
807
808# Regular parameter description.
809class ParamDesc(object):
810    def __init__(self, ptype_str, ptype, *args, **kwargs):
811        self.ptype_str = ptype_str
812        # remember ptype only if it is provided
813        if ptype != None:
814            self.ptype = ptype
815
816        if args:
817            if len(args) == 1:
818                self.desc = args[0]
819            elif len(args) == 2:
820                self.default = args[0]
821                self.desc = args[1]
822            else:
823                raise TypeError, 'too many arguments'
824
825        if kwargs.has_key('desc'):
826            assert(not hasattr(self, 'desc'))
827            self.desc = kwargs['desc']
828            del kwargs['desc']
829
830        if kwargs.has_key('default'):
831            assert(not hasattr(self, 'default'))
832            self.default = kwargs['default']
833            del kwargs['default']
834
835        if kwargs:
836            raise TypeError, 'extra unknown kwargs %s' % kwargs
837
838        if not hasattr(self, 'desc'):
839            raise TypeError, 'desc attribute missing'
840
841    def __getattr__(self, attr):
842        if attr == 'ptype':
843            try:
844                ptype = eval(self.ptype_str, m5.objects.__dict__)
845                if not isinstance(ptype, type):
846                    panic("Param qualifier is not a type: %s" % self.ptype)
847                self.ptype = ptype
848                return ptype
849            except NameError:
850                pass
851        raise AttributeError, "'%s' object has no attribute '%s'" % \
852              (type(self).__name__, attr)
853
854    def convert(self, value):
855        if isinstance(value, BaseProxy):
856            value.set_param_desc(self)
857            return value
858        if not hasattr(self, 'ptype') and isNullPointer(value):
859            # deferred evaluation of SimObject; continue to defer if
860            # we're just assigning a null pointer
861            return value
862        if isinstance(value, self.ptype):
863            return value
864        if isNullPointer(value) and issubclass(self.ptype, SimObject):
865            return value
866        return self.ptype(value)
867
868# Vector-valued parameter description.  Just like ParamDesc, except
869# that the value is a vector (list) of the specified type instead of a
870# single value.
871
872class VectorParamValue(list):
873    def ini_str(self):
874        return ' '.join([v.ini_str() for v in self])
875
876    def unproxy(self, base):
877        return [v.unproxy(base) for v in self]
878
879class SimObjVector(VectorParamValue):
880    def print_ini(self):
881        for v in self:
882            v.print_ini()
883
884class VectorParamDesc(ParamDesc):
885    # Convert assigned value to appropriate type.  If the RHS is not a
886    # list or tuple, it generates a single-element list.
887    def convert(self, value):
888        if isinstance(value, (list, tuple)):
889            # list: coerce each element into new list
890            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
891            if isSimObjSequence(tmp_list):
892                return SimObjVector(tmp_list)
893            else:
894                return VectorParamValue(tmp_list)
895        else:
896            # singleton: leave it be (could coerce to a single-element
897            # list here, but for some historical reason we don't...
898            return ParamDesc.convert(self, value)
899
900
901class ParamFactory(object):
902    def __init__(self, param_desc_class, ptype_str = None):
903        self.param_desc_class = param_desc_class
904        self.ptype_str = ptype_str
905
906    def __getattr__(self, attr):
907        if self.ptype_str:
908            attr = self.ptype_str + '.' + attr
909        return ParamFactory(self.param_desc_class, attr)
910
911    # E.g., Param.Int(5, "number of widgets")
912    def __call__(self, *args, **kwargs):
913        caller_frame = inspect.currentframe().f_back
914        ptype = None
915        try:
916            ptype = eval(self.ptype_str,
917                         caller_frame.f_globals, caller_frame.f_locals)
918            if not isinstance(ptype, type):
919                raise TypeError, \
920                      "Param qualifier is not a type: %s" % ptype
921        except NameError:
922            # if name isn't defined yet, assume it's a SimObject, and
923            # try to resolve it later
924            pass
925        return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs)
926
927Param = ParamFactory(ParamDesc)
928VectorParam = ParamFactory(VectorParamDesc)
929
930#####################################################################
931#
932# Parameter Types
933#
934# Though native Python types could be used to specify parameter types
935# (the 'ptype' field of the Param and VectorParam classes), it's more
936# flexible to define our own set of types.  This gives us more control
937# over how Python expressions are converted to values (via the
938# __init__() constructor) and how these values are printed out (via
939# the __str__() conversion method).  Eventually we'll need these types
940# to correspond to distinct C++ types as well.
941#
942#####################################################################
943
944# superclass for "numeric" parameter values, to emulate math
945# operations in a type-safe way.  e.g., a Latency times an int returns
946# a new Latency object.
947class NumericParamValue(ParamValue):
948    def __str__(self):
949        return str(self.value)
950
951    def __float__(self):
952        return float(self.value)
953
954    # hook for bounds checking
955    def _check(self):
956        return
957
958    def __mul__(self, other):
959        newobj = self.__class__(self)
960        newobj.value *= other
961        newobj._check()
962        return newobj
963
964    __rmul__ = __mul__
965
966    def __div__(self, other):
967        newobj = self.__class__(self)
968        newobj.value /= other
969        newobj._check()
970        return newobj
971
972    def __sub__(self, other):
973        newobj = self.__class__(self)
974        newobj.value -= other
975        newobj._check()
976        return newobj
977
978class Range(ParamValue):
979    type = int # default; can be overridden in subclasses
980    def __init__(self, *args, **kwargs):
981
982        def handle_kwargs(self, kwargs):
983            if 'end' in kwargs:
984                self.second = self.type(kwargs.pop('end'))
985            elif 'size' in kwargs:
986                self.second = self.first + self.type(kwargs.pop('size')) - 1
987            else:
988                raise TypeError, "Either end or size must be specified"
989
990        if len(args) == 0:
991            self.first = self.type(kwargs.pop('start'))
992            handle_kwargs(self, kwargs)
993
994        elif len(args) == 1:
995            if kwargs:
996                self.first = self.type(args[0])
997                handle_kwargs(self, kwargs)
998            elif isinstance(args[0], Range):
999                self.first = self.type(args[0].first)
1000                self.second = self.type(args[0].second)
1001            else:
1002                self.first = self.type(0)
1003                self.second = self.type(args[0]) - 1
1004
1005        elif len(args) == 2:
1006            self.first = self.type(args[0])
1007            self.second = self.type(args[1])
1008        else:
1009            raise TypeError, "Too many arguments specified"
1010
1011        if kwargs:
1012            raise TypeError, "too many keywords: %s" % kwargs.keys()
1013
1014    def __str__(self):
1015        return '%s:%s' % (self.first, self.second)
1016
1017# Metaclass for bounds-checked integer parameters.  See CheckedInt.
1018class CheckedIntType(type):
1019    def __init__(cls, name, bases, dict):
1020        super(CheckedIntType, cls).__init__(name, bases, dict)
1021
1022        # CheckedInt is an abstract base class, so we actually don't
1023        # want to do any processing on it... the rest of this code is
1024        # just for classes that derive from CheckedInt.
1025        if name == 'CheckedInt':
1026            return
1027
1028        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
1029            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
1030                panic("CheckedInt subclass %s must define either\n" \
1031                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
1032                      % name);
1033            if cls.unsigned:
1034                cls.min = 0
1035                cls.max = 2 ** cls.size - 1
1036            else:
1037                cls.min = -(2 ** (cls.size - 1))
1038                cls.max = (2 ** (cls.size - 1)) - 1
1039
1040# Abstract superclass for bounds-checked integer parameters.  This
1041# class is subclassed to generate parameter classes with specific
1042# bounds.  Initialization of the min and max bounds is done in the
1043# metaclass CheckedIntType.__init__.
1044class CheckedInt(NumericParamValue):
1045    __metaclass__ = CheckedIntType
1046
1047    def _check(self):
1048        if not self.min <= self.value <= self.max:
1049            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
1050                  (self.min, self.value, self.max)
1051
1052    def __init__(self, value):
1053        if isinstance(value, str):
1054            self.value = toInteger(value)
1055        elif isinstance(value, (int, long, float)):
1056            self.value = long(value)
1057        self._check()
1058
1059class Int(CheckedInt):      size = 32; unsigned = False
1060class Unsigned(CheckedInt): size = 32; unsigned = True
1061
1062class Int8(CheckedInt):     size =  8; unsigned = False
1063class UInt8(CheckedInt):    size =  8; unsigned = True
1064class Int16(CheckedInt):    size = 16; unsigned = False
1065class UInt16(CheckedInt):   size = 16; unsigned = True
1066class Int32(CheckedInt):    size = 32; unsigned = False
1067class UInt32(CheckedInt):   size = 32; unsigned = True
1068class Int64(CheckedInt):    size = 64; unsigned = False
1069class UInt64(CheckedInt):   size = 64; unsigned = True
1070
1071class Counter(CheckedInt):  size = 64; unsigned = True
1072class Tick(CheckedInt):     size = 64; unsigned = True
1073class TcpPort(CheckedInt):  size = 16; unsigned = True
1074class UdpPort(CheckedInt):  size = 16; unsigned = True
1075
1076class Percent(CheckedInt):  min = 0; max = 100
1077
1078class Float(ParamValue, float):
1079    pass
1080
1081class MemorySize(CheckedInt):
1082    size = 64
1083    unsigned = True
1084    def __init__(self, value):
1085        if isinstance(value, MemorySize):
1086            self.value = value.value
1087        else:
1088            self.value = toMemorySize(value)
1089        self._check()
1090
1091class MemorySize32(CheckedInt):
1092    size = 32
1093    unsigned = True
1094    def __init__(self, value):
1095        if isinstance(value, MemorySize):
1096            self.value = value.value
1097        else:
1098            self.value = toMemorySize(value)
1099        self._check()
1100
1101class Addr(CheckedInt):
1102    size = 64
1103    unsigned = True
1104    def __init__(self, value):
1105        if isinstance(value, Addr):
1106            self.value = value.value
1107        else:
1108            try:
1109                self.value = toMemorySize(value)
1110            except TypeError:
1111                self.value = long(value)
1112        self._check()
1113
1114class AddrRange(Range):
1115    type = Addr
1116
1117# String-valued parameter.  Just mixin the ParamValue class
1118# with the built-in str class.
1119class String(ParamValue,str):
1120    pass
1121
1122# Boolean parameter type.  Python doesn't let you subclass bool, since
1123# it doesn't want to let you create multiple instances of True and
1124# False.  Thus this is a little more complicated than String.
1125class Bool(ParamValue):
1126    def __init__(self, value):
1127        try:
1128            self.value = toBool(value)
1129        except TypeError:
1130            self.value = bool(value)
1131
1132    def __str__(self):
1133        return str(self.value)
1134
1135    def ini_str(self):
1136        if self.value:
1137            return 'true'
1138        return 'false'
1139
1140def IncEthernetAddr(addr, val = 1):
1141    bytes = map(lambda x: int(x, 16), addr.split(':'))
1142    bytes[5] += val
1143    for i in (5, 4, 3, 2, 1):
1144        val,rem = divmod(bytes[i], 256)
1145        bytes[i] = rem
1146        if val == 0:
1147            break
1148        bytes[i - 1] += val
1149    assert(bytes[0] <= 255)
1150    return ':'.join(map(lambda x: '%02x' % x, bytes))
1151
1152class NextEthernetAddr(object):
1153    addr = "00:90:00:00:00:01"
1154
1155    def __init__(self, inc = 1):
1156        self.value = NextEthernetAddr.addr
1157        NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc)
1158
1159class EthernetAddr(ParamValue):
1160    def __init__(self, value):
1161        if value == NextEthernetAddr:
1162            self.value = value
1163            return
1164
1165        if not isinstance(value, str):
1166            raise TypeError, "expected an ethernet address and didn't get one"
1167
1168        bytes = value.split(':')
1169        if len(bytes) != 6:
1170            raise TypeError, 'invalid ethernet address %s' % value
1171
1172        for byte in bytes:
1173            if not 0 <= int(byte) <= 256:
1174                raise TypeError, 'invalid ethernet address %s' % value
1175
1176        self.value = value
1177
1178    def unproxy(self, base):
1179        if self.value == NextEthernetAddr:
1180            self.addr = self.value().value
1181        return self
1182
1183    def __str__(self):
1184        if self.value == NextEthernetAddr:
1185            if hasattr(self, 'addr'):
1186                return self.addr
1187            else:
1188                return "NextEthernetAddr (unresolved)"
1189        else:
1190            return self.value
1191
1192# Special class for NULL pointers.  Note the special check in
1193# make_param_value() above that lets these be assigned where a
1194# SimObject is required.
1195# only one copy of a particular node
1196class NullSimObject(object):
1197    __metaclass__ = Singleton
1198
1199    def __call__(cls):
1200        return cls
1201
1202    def _instantiate(self, parent = None, path = ''):
1203        pass
1204
1205    def ini_str(self):
1206        return 'Null'
1207
1208    def unproxy(self, base):
1209        return self
1210
1211    def set_path(self, parent, name):
1212        pass
1213    def __str__(self):
1214        return 'Null'
1215
1216# The only instance you'll ever need...
1217Null = NULL = NullSimObject()
1218
1219# Enumerated types are a little more complex.  The user specifies the
1220# type as Enum(foo) where foo is either a list or dictionary of
1221# alternatives (typically strings, but not necessarily so).  (In the
1222# long run, the integer value of the parameter will be the list index
1223# or the corresponding dictionary value.  For now, since we only check
1224# that the alternative is valid and then spit it into a .ini file,
1225# there's not much point in using the dictionary.)
1226
1227# What Enum() must do is generate a new type encapsulating the
1228# provided list/dictionary so that specific values of the parameter
1229# can be instances of that type.  We define two hidden internal
1230# classes (_ListEnum and _DictEnum) to serve as base classes, then
1231# derive the new type from the appropriate base class on the fly.
1232
1233
1234# Metaclass for Enum types
1235class MetaEnum(type):
1236    def __init__(cls, name, bases, init_dict):
1237        if init_dict.has_key('map'):
1238            if not isinstance(cls.map, dict):
1239                raise TypeError, "Enum-derived class attribute 'map' " \
1240                      "must be of type dict"
1241            # build list of value strings from map
1242            cls.vals = cls.map.keys()
1243            cls.vals.sort()
1244        elif init_dict.has_key('vals'):
1245            if not isinstance(cls.vals, list):
1246                raise TypeError, "Enum-derived class attribute 'vals' " \
1247                      "must be of type list"
1248            # build string->value map from vals sequence
1249            cls.map = {}
1250            for idx,val in enumerate(cls.vals):
1251                cls.map[val] = idx
1252        else:
1253            raise TypeError, "Enum-derived class must define "\
1254                  "attribute 'map' or 'vals'"
1255
1256        super(MetaEnum, cls).__init__(name, bases, init_dict)
1257
1258    def cpp_declare(cls):
1259        s = 'enum %s {\n    ' % cls.__name__
1260        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
1261        s += '\n};\n'
1262        return s
1263
1264# Base class for enum types.
1265class Enum(ParamValue):
1266    __metaclass__ = MetaEnum
1267    vals = []
1268
1269    def __init__(self, value):
1270        if value not in self.map:
1271            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1272                  % (value, self.vals)
1273        self.value = value
1274
1275    def __str__(self):
1276        return self.value
1277
1278ticks_per_sec = None
1279
1280# how big does a rounding error need to be before we warn about it?
1281frequency_tolerance = 0.001  # 0.1%
1282
1283# convert a floting-point # of ticks to integer, and warn if rounding
1284# discards too much precision
1285def tick_check(float_ticks):
1286    if float_ticks == 0:
1287        return 0
1288    int_ticks = int(round(float_ticks))
1289    err = (float_ticks - int_ticks) / float_ticks
1290    if err > frequency_tolerance:
1291        print >> sys.stderr, "Warning: rounding error > tolerance"
1292        print >> sys.stderr, "    %f rounded to %d" % (float_ticks, int_ticks)
1293        #raise ValueError
1294    return int_ticks
1295
1296def getLatency(value):
1297    if isinstance(value, Latency) or isinstance(value, Clock):
1298        return value.value
1299    elif isinstance(value, Frequency) or isinstance(value, RootClock):
1300        return 1 / value.value
1301    elif isinstance(value, str):
1302        try:
1303            return toLatency(value)
1304        except ValueError:
1305            try:
1306                return 1 / toFrequency(value)
1307            except ValueError:
1308                pass # fall through
1309    raise ValueError, "Invalid Frequency/Latency value '%s'" % value
1310
1311
1312class Latency(NumericParamValue):
1313    def __init__(self, value):
1314        self.value = getLatency(value)
1315
1316    def __getattr__(self, attr):
1317        if attr in ('latency', 'period'):
1318            return self
1319        if attr == 'frequency':
1320            return Frequency(self)
1321        raise AttributeError, "Latency object has no attribute '%s'" % attr
1322
1323    # convert latency to ticks
1324    def ini_str(self):
1325        return str(tick_check(self.value * ticks_per_sec))
1326
1327class Frequency(NumericParamValue):
1328    def __init__(self, value):
1329        self.value = 1 / getLatency(value)
1330
1331    def __getattr__(self, attr):
1332        if attr == 'frequency':
1333            return self
1334        if attr in ('latency', 'period'):
1335            return Latency(self)
1336        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1337
1338    # convert frequency to ticks per period
1339    def ini_str(self):
1340        return self.period.ini_str()
1341
1342# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz).
1343# We can't inherit from Frequency because we don't want it to be directly
1344# assignable to a regular Frequency parameter.
1345class RootClock(ParamValue):
1346    def __init__(self, value):
1347        self.value = 1 / getLatency(value)
1348
1349    def __getattr__(self, attr):
1350        if attr == 'frequency':
1351            return Frequency(self)
1352        if attr in ('latency', 'period'):
1353            return Latency(self)
1354        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1355
1356    def ini_str(self):
1357        return str(tick_check(self.value))
1358
1359# A generic frequency and/or Latency value.  Value is stored as a latency,
1360# but to avoid ambiguity this object does not support numeric ops (* or /).
1361# An explicit conversion to a Latency or Frequency must be made first.
1362class Clock(ParamValue):
1363    def __init__(self, value):
1364        self.value = getLatency(value)
1365
1366    def __getattr__(self, attr):
1367        if attr == 'frequency':
1368            return Frequency(self)
1369        if attr in ('latency', 'period'):
1370            return Latency(self)
1371        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1372
1373    def ini_str(self):
1374        return self.period.ini_str()
1375
1376class NetworkBandwidth(float,ParamValue):
1377    def __new__(cls, value):
1378        val = toNetworkBandwidth(value) / 8.0
1379        return super(cls, NetworkBandwidth).__new__(cls, val)
1380
1381    def __str__(self):
1382        return str(self.val)
1383
1384    def ini_str(self):
1385        return '%f' % (ticks_per_sec / float(self))
1386
1387class MemoryBandwidth(float,ParamValue):
1388    def __new__(self, value):
1389        val = toMemoryBandwidth(value)
1390        return super(cls, MemoryBandwidth).__new__(cls, val)
1391
1392    def __str__(self):
1393        return str(self.val)
1394
1395    def ini_str(self):
1396        return '%f' % (ticks_per_sec / float(self))
1397
1398#
1399# "Constants"... handy aliases for various values.
1400#
1401
1402# Some memory range specifications use this as a default upper bound.
1403MaxAddr = Addr.max
1404MaxTick = Tick.max
1405AllMemory = AddrRange(0, MaxAddr)
1406
1407#####################################################################
1408
1409# __all__ defines the list of symbols that get exported when
1410# 'from config import *' is invoked.  Try to keep this reasonably
1411# short to avoid polluting other namespaces.
1412__all__ = ['SimObject', 'ParamContext', 'Param', 'VectorParam',
1413           'Parent', 'Self',
1414           'Enum', 'Bool', 'String', 'Float',
1415           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1416           'Int32', 'UInt32', 'Int64', 'UInt64',
1417           'Counter', 'Addr', 'Tick', 'Percent',
1418           'TcpPort', 'UdpPort', 'EthernetAddr',
1419           'MemorySize', 'MemorySize32',
1420           'Latency', 'Frequency', 'RootClock', 'Clock',
1421           'NetworkBandwidth', 'MemoryBandwidth',
1422           'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory',
1423           'Null', 'NULL',
1424           'NextEthernetAddr']
1425
1426