config.py revision 2714:baa3445b7b9e
1# Copyright (c) 2004-2005 The Regents of The University of Michigan
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27# Authors: Steve Reinhardt
28#          Nathan Binkert
29
30import os, re, sys, types, inspect
31
32import m5
33from m5 import panic
34from convert import *
35from multidict import multidict
36
37noDot = False
38try:
39    import pydot
40except:
41    noDot = True
42
43class Singleton(type):
44    def __call__(cls, *args, **kwargs):
45        if hasattr(cls, '_instance'):
46            return cls._instance
47
48        cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
49        return cls._instance
50
51#####################################################################
52#
53# M5 Python Configuration Utility
54#
55# The basic idea is to write simple Python programs that build Python
56# objects corresponding to M5 SimObjects for the desired simulation
57# configuration.  For now, the Python emits a .ini file that can be
58# parsed by M5.  In the future, some tighter integration between M5
59# and the Python interpreter may allow bypassing the .ini file.
60#
61# Each SimObject class in M5 is represented by a Python class with the
62# same name.  The Python inheritance tree mirrors the M5 C++ tree
63# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
64# SimObjects inherit from a single SimObject base class).  To specify
65# an instance of an M5 SimObject in a configuration, the user simply
66# instantiates the corresponding Python object.  The parameters for
67# that SimObject are given by assigning to attributes of the Python
68# object, either using keyword assignment in the constructor or in
69# separate assignment statements.  For example:
70#
71# cache = BaseCache(size='64KB')
72# cache.hit_latency = 3
73# cache.assoc = 8
74#
75# The magic lies in the mapping of the Python attributes for SimObject
76# classes to the actual SimObject parameter specifications.  This
77# allows parameter validity checking in the Python code.  Continuing
78# the example above, the statements "cache.blurfl=3" or
79# "cache.assoc='hello'" would both result in runtime errors in Python,
80# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
81# parameter requires an integer, respectively.  This magic is done
82# primarily by overriding the special __setattr__ method that controls
83# assignment to object attributes.
84#
85# Once a set of Python objects have been instantiated in a hierarchy,
86# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
87# will generate a .ini file.  See simple-4cpu.py for an example
88# (corresponding to m5-test/simple-4cpu.ini).
89#
90#####################################################################
91
92#####################################################################
93#
94# ConfigNode/SimObject classes
95#
96# The Python class hierarchy rooted by ConfigNode (which is the base
97# class of SimObject, which in turn is the base class of all other M5
98# SimObject classes) has special attribute behavior.  In general, an
99# object in this hierarchy has three categories of attribute-like
100# things:
101#
102# 1. Regular Python methods and variables.  These must start with an
103# underscore to be treated normally.
104#
105# 2. SimObject parameters.  These values are stored as normal Python
106# attributes, but all assignments to these attributes are checked
107# against the pre-defined set of parameters stored in the class's
108# _params dictionary.  Assignments to attributes that do not
109# correspond to predefined parameters, or that are not of the correct
110# type, incur runtime errors.
111#
112# 3. Hierarchy children.  The child nodes of a ConfigNode are stored
113# in the node's _children dictionary, but can be accessed using the
114# Python attribute dot-notation (just as they are printed out by the
115# simulator).  Children cannot be created using attribute assigment;
116# they must be added by specifying the parent node in the child's
117# constructor or using the '+=' operator.
118
119# The SimObject parameters are the most complex, for a few reasons.
120# First, both parameter descriptions and parameter values are
121# inherited.  Thus parameter description lookup must go up the
122# inheritance chain like normal attribute lookup, but this behavior
123# must be explicitly coded since the lookup occurs in each class's
124# _params attribute.  Second, because parameter values can be set
125# on SimObject classes (to implement default values), the parameter
126# checking behavior must be enforced on class attribute assignments as
127# well as instance attribute assignments.  Finally, because we allow
128# class specialization via inheritance (e.g., see the L1Cache class in
129# the simple-4cpu.py example), we must do parameter checking even on
130# class instantiation.  To provide all these features, we use a
131# metaclass to define most of the SimObject parameter behavior for
132# this class hierarchy.
133#
134#####################################################################
135
136def isSimObject(value):
137    return isinstance(value, SimObject)
138
139def isSimObjectClass(value):
140    try:
141        return issubclass(value, SimObject)
142    except TypeError:
143        # happens if value is not a class at all
144        return False
145
146def isSimObjectSequence(value):
147    if not isinstance(value, (list, tuple)) or len(value) == 0:
148        return False
149
150    for val in value:
151        if not isNullPointer(val) and not isSimObject(val):
152            return False
153
154    return True
155
156def isSimObjectClassSequence(value):
157    if not isinstance(value, (list, tuple)) or len(value) == 0:
158        return False
159
160    for val in value:
161        if not isNullPointer(val) and not isSimObjectClass(val):
162            return False
163
164    return True
165
166def isSimObjectOrSequence(value):
167    return isSimObject(value) or isSimObjectSequence(value)
168
169def isSimObjectClassOrSequence(value):
170    return isSimObjectClass(value) or isSimObjectClassSequence(value)
171
172def isNullPointer(value):
173    return isinstance(value, NullSimObject)
174
175# Apply method to object.
176# applyMethod(obj, 'meth', <args>) is equivalent to obj.meth(<args>)
177def applyMethod(obj, meth, *args, **kwargs):
178    return getattr(obj, meth)(*args, **kwargs)
179
180# If the first argument is an (non-sequence) object, apply the named
181# method with the given arguments.  If the first argument is a
182# sequence, apply the method to each element of the sequence (a la
183# 'map').
184def applyOrMap(objOrSeq, meth, *args, **kwargs):
185    if not isinstance(objOrSeq, (list, tuple)):
186        return applyMethod(objOrSeq, meth, *args, **kwargs)
187    else:
188        return [applyMethod(o, meth, *args, **kwargs) for o in objOrSeq]
189
190
191# The metaclass for ConfigNode (and thus for everything that derives
192# from ConfigNode, including SimObject).  This class controls how new
193# classes that derive from ConfigNode are instantiated, and provides
194# inherited class behavior (just like a class controls how instances
195# of that class are instantiated, and provides inherited instance
196# behavior).
197class MetaSimObject(type):
198    # Attributes that can be set only at initialization time
199    init_keywords = { 'abstract' : types.BooleanType,
200                      'type' : types.StringType }
201    # Attributes that can be set any time
202    keywords = { 'check' : types.FunctionType,
203                 'children' : types.ListType }
204
205    # __new__ is called before __init__, and is where the statements
206    # in the body of the class definition get loaded into the class's
207    # __dict__.  We intercept this to filter out parameter assignments
208    # and only allow "private" attributes to be passed to the base
209    # __new__ (starting with underscore).
210    def __new__(mcls, name, bases, dict):
211        if dict.has_key('_init_dict'):
212            # must have been called from makeSubclass() rather than
213            # via Python class declaration; bypass filtering process.
214            cls_dict = dict
215        else:
216            # Copy "private" attributes (including special methods
217            # such as __new__) to the official dict.  Everything else
218            # goes in _init_dict to be filtered in __init__.
219            cls_dict = {}
220            for key,val in dict.items():
221                if key.startswith('_'):
222                    cls_dict[key] = val
223                    del dict[key]
224            cls_dict['_init_dict'] = dict
225        return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
226
227    # subclass initialization
228    def __init__(cls, name, bases, dict):
229        # calls type.__init__()... I think that's a no-op, but leave
230        # it here just in case it's not.
231        super(MetaSimObject, cls).__init__(name, bases, dict)
232
233        # initialize required attributes
234        cls._params = multidict()
235        cls._values = multidict()
236        cls._instantiated = False # really instantiated or subclassed
237        cls._anon_subclass_counter = 0
238
239        # We don't support multiple inheritance.  If you want to, you
240        # must fix multidict to deal with it properly.
241        if len(bases) > 1:
242            raise TypeError, "SimObjects do not support multiple inheritance"
243
244        base = bases[0]
245
246        # the only time the following is not true is when we define
247        # the SimObject class itself
248        if isinstance(base, MetaSimObject):
249            cls._params.parent = base._params
250            cls._values.parent = base._values
251            base._instantiated = True
252
253        # now process the _init_dict items
254        for key,val in cls._init_dict.items():
255            if isinstance(val, (types.FunctionType, types.TypeType)):
256                type.__setattr__(cls, key, val)
257
258            # param descriptions
259            elif isinstance(val, ParamDesc):
260                cls._new_param(key, val)
261
262            # init-time-only keywords
263            elif cls.init_keywords.has_key(key):
264                cls._set_keyword(key, val, cls.init_keywords[key])
265
266            # default: use normal path (ends up in __setattr__)
267            else:
268                setattr(cls, key, val)
269
270        # Pull the deep-copy memoization dict out of the class dict if
271        # it's there...
272        memo = cls.__dict__.get('_memo', {})
273
274        # Handle SimObject values
275        for key,val in cls._values.iteritems():
276            # SimObject instances need to be promoted to classes.
277            # Existing classes should not have any instance values, so
278            # these can only occur at the lowest level dict (the
279            # parameters just being set in this class definition).
280            if isSimObjectOrSequence(val):
281                assert(val == cls._values.local[key])
282                cls._values[key] = applyOrMap(val, 'makeClass', memo)
283            # SimObject classes need to be subclassed so that
284            # parameters that get set at this level only affect this
285            # level and derivatives.
286            elif isSimObjectClassOrSequence(val):
287                assert(not cls._values.local.has_key(key))
288                cls._values[key] = applyOrMap(val, 'makeSubclass', {}, memo)
289
290
291    def _set_keyword(cls, keyword, val, kwtype):
292        if not isinstance(val, kwtype):
293            raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
294                  (keyword, type(val), kwtype)
295        if isinstance(val, types.FunctionType):
296            val = classmethod(val)
297        type.__setattr__(cls, keyword, val)
298
299    def _new_param(cls, name, value):
300        cls._params[name] = value
301        if hasattr(value, 'default'):
302            setattr(cls, name, value.default)
303
304    # Set attribute (called on foo.attr = value when foo is an
305    # instance of class cls).
306    def __setattr__(cls, attr, value):
307        # normal processing for private attributes
308        if attr.startswith('_'):
309            type.__setattr__(cls, attr, value)
310            return
311
312        if cls.keywords.has_key(attr):
313            cls._set_keyword(attr, value, cls.keywords[attr])
314            return
315
316        # must be SimObject param
317        param = cls._params.get(attr, None)
318        if param:
319            # It's ok: set attribute by delegating to 'object' class.
320            if isSimObjectOrSequence(value) and cls._instantiated:
321                raise AttributeError, \
322                  "Cannot set SimObject parameter '%s' after\n" \
323                  "    class %s has been instantiated or subclassed" \
324                  % (attr, cls.__name__)
325            try:
326                cls._values[attr] = param.convert(value)
327            except Exception, e:
328                msg = "%s\nError setting param %s.%s to %s\n" % \
329                      (e, cls.__name__, attr, value)
330                e.args = (msg, )
331                raise
332        # I would love to get rid of this
333        elif isSimObjectOrSequence(value):
334           cls._values[attr] = value
335        else:
336            raise AttributeError, \
337                  "Class %s has no parameter %s" % (cls.__name__, attr)
338
339    def __getattr__(cls, attr):
340        if cls._values.has_key(attr):
341            return cls._values[attr]
342
343        raise AttributeError, \
344              "object '%s' has no attribute '%s'" % (cls.__name__, attr)
345
346    # Create a subclass of this class.  Basically a function interface
347    # to the standard Python class definition mechanism, primarily for
348    # internal use.  'memo' dict param supports "deep copy" (really
349    # "deep subclass") operations... within a given operation,
350    # multiple references to a class should result in a single
351    # subclass object with multiple references to it (as opposed to
352    # mutiple unique subclasses).
353    def makeSubclass(cls, init_dict, memo = {}):
354        subcls = memo.get(cls)
355        if not subcls:
356            name = cls.__name__ + '_' + str(cls._anon_subclass_counter)
357            cls._anon_subclass_counter += 1
358            subcls = MetaSimObject(name, (cls,),
359                                   { '_init_dict': init_dict, '_memo': memo })
360        return subcls
361
362# The ConfigNode class is the root of the special hierarchy.  Most of
363# the code in this class deals with the configuration hierarchy itself
364# (parent/child node relationships).
365class SimObject(object):
366    # Specify metaclass.  Any class inheriting from SimObject will
367    # get this metaclass.
368    __metaclass__ = MetaSimObject
369
370    # __new__ operator allocates new instances of the class.  We
371    # override it here just to support "deep instantiation" operation
372    # via the _memo dict.  When recursively instantiating an object
373    # hierarchy we want to make sure that each class is instantiated
374    # only once, and that if there are multiple references to the same
375    # original class, we end up with the corresponding instantiated
376    # references all pointing to the same instance.
377    def __new__(cls, _memo = None, **kwargs):
378        if _memo is not None and _memo.has_key(cls):
379            # return previously instantiated object
380            assert(len(kwargs) == 0)
381            return _memo[cls]
382        else:
383            # Need a new one... if it needs to be memoized, this will
384            # happen in __init__.  We defer the insertion until then
385            # so __init__ can use the memo dict to tell whether or not
386            # to perform the initialization.
387            return super(SimObject, cls).__new__(cls, **kwargs)
388
389    # Initialize new instance previously allocated by __new__.  For
390    # objects with SimObject-valued params, we need to recursively
391    # instantiate the classes represented by those param values as
392    # well (in a consistent "deep copy"-style fashion; see comment
393    # above).
394    def __init__(self, _memo = None, **kwargs):
395        if _memo is not None:
396            # We're inside a "deep instantiation"
397            assert(isinstance(_memo, dict))
398            assert(len(kwargs) == 0)
399            if _memo.has_key(self.__class__):
400                # __new__ returned an existing, already initialized
401                # instance, so there's nothing to do here
402                assert(_memo[self.__class__] == self)
403                return
404            # no pre-existing object, so remember this one here
405            _memo[self.__class__] = self
406        else:
407            # This is a new top-level instantiation... don't memoize
408            # this objcet, but prepare to memoize any recursively
409            # instantiated objects.
410            _memo = {}
411
412        self.__class__._instantiated = True
413
414        self._children = {}
415        # Inherit parameter values from class using multidict so
416        # individual value settings can be overridden.
417        self._values = multidict(self.__class__._values)
418        # For SimObject-valued parameters, the class should have
419        # classes (not instances) for the values.  We need to
420        # instantiate these classes rather than just inheriting the
421        # class object.
422        for key,val in self.__class__._values.iteritems():
423            if isSimObjectClass(val):
424                setattr(self, key, val(_memo))
425            elif isSimObjectClassSequence(val) and len(val):
426                setattr(self, key, [ v(_memo) for v in val ])
427        # apply attribute assignments from keyword args, if any
428        for key,val in kwargs.iteritems():
429            setattr(self, key, val)
430
431    # Use this instance as a template to create a new class.
432    def makeClass(self, memo = {}):
433        cls = memo.get(self)
434        if not cls:
435            cls =  self.__class__.makeSubclass(self._values.local)
436            memo[self] = cls
437        return cls
438
439    # Direct instantiation of instances (cloning) is no longer
440    # allowed; must generate class from instance first.
441    def __call__(self, **kwargs):
442        raise TypeError, "cannot instantiate SimObject; "\
443              "use makeClass() to make class first"
444
445    def __getattr__(self, attr):
446        if self._values.has_key(attr):
447            return self._values[attr]
448
449        raise AttributeError, "object '%s' has no attribute '%s'" \
450              % (self.__class__.__name__, attr)
451
452    # Set attribute (called on foo.attr = value when foo is an
453    # instance of class cls).
454    def __setattr__(self, attr, value):
455        # normal processing for private attributes
456        if attr.startswith('_'):
457            object.__setattr__(self, attr, value)
458            return
459
460        # must be SimObject param
461        param = self._params.get(attr, None)
462        if param:
463            # It's ok: set attribute by delegating to 'object' class.
464            try:
465                value = param.convert(value)
466            except Exception, e:
467                msg = "%s\nError setting param %s.%s to %s\n" % \
468                      (e, self.__class__.__name__, attr, value)
469                e.args = (msg, )
470                raise
471        # I would love to get rid of this
472        elif isSimObjectOrSequence(value):
473            pass
474        else:
475            raise AttributeError, "Class %s has no parameter %s" \
476                  % (self.__class__.__name__, attr)
477
478        # clear out old child with this name, if any
479        self.clear_child(attr)
480
481        if isSimObject(value):
482            value.set_path(self, attr)
483        elif isSimObjectSequence(value):
484            value = SimObjVector(value)
485            [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)]
486
487        self._values[attr] = value
488
489    # this hack allows tacking a '[0]' onto parameters that may or may
490    # not be vectors, and always getting the first element (e.g. cpus)
491    def __getitem__(self, key):
492        if key == 0:
493            return self
494        raise TypeError, "Non-zero index '%s' to SimObject" % key
495
496    # clear out children with given name, even if it's a vector
497    def clear_child(self, name):
498        if not self._children.has_key(name):
499            return
500        child = self._children[name]
501        if isinstance(child, SimObjVector):
502            for i in xrange(len(child)):
503                del self._children["s%d" % (name, i)]
504        del self._children[name]
505
506    def add_child(self, name, value):
507        self._children[name] = value
508
509    def set_path(self, parent, name):
510        if not hasattr(self, '_parent'):
511            self._parent = parent
512            self._name = name
513            parent.add_child(name, self)
514
515    def path(self):
516        if not hasattr(self, '_parent'):
517            return 'root'
518        ppath = self._parent.path()
519        if ppath == 'root':
520            return self._name
521        return ppath + "." + self._name
522
523    def __str__(self):
524        return self.path()
525
526    def ini_str(self):
527        return self.path()
528
529    def find_any(self, ptype):
530        if isinstance(self, ptype):
531            return self, True
532
533        found_obj = None
534        for child in self._children.itervalues():
535            if isinstance(child, ptype):
536                if found_obj != None and child != found_obj:
537                    raise AttributeError, \
538                          'parent.any matched more than one: %s %s' % \
539                          (found_obj.path, child.path)
540                found_obj = child
541        # search param space
542        for pname,pdesc in self._params.iteritems():
543            if issubclass(pdesc.ptype, ptype):
544                match_obj = self._values[pname]
545                if found_obj != None and found_obj != match_obj:
546                    raise AttributeError, \
547                          'parent.any matched more than one: %s' % obj.path
548                found_obj = match_obj
549        return found_obj, found_obj != None
550
551    def unproxy(self, base):
552        return self
553
554    def print_ini(self):
555        print '[' + self.path() + ']'	# .ini section header
556
557        if hasattr(self, 'type') and not isinstance(self, ParamContext):
558            print 'type=%s' % self.type
559
560        child_names = self._children.keys()
561        child_names.sort()
562        np_child_names = [c for c in child_names \
563                          if not isinstance(self._children[c], ParamContext)]
564        if len(np_child_names):
565            print 'children=%s' % ' '.join(np_child_names)
566
567        param_names = self._params.keys()
568        param_names.sort()
569        for param in param_names:
570            value = self._values.get(param, None)
571            if value != None:
572                if isproxy(value):
573                    try:
574                        value = value.unproxy(self)
575                    except:
576                        print >> sys.stderr, \
577                              "Error in unproxying param '%s' of %s" % \
578                              (param, self.path())
579                        raise
580                    setattr(self, param, value)
581                print '%s=%s' % (param, self._values[param].ini_str())
582
583        print	# blank line between objects
584
585        for child in child_names:
586            self._children[child].print_ini()
587
588    # generate output file for 'dot' to display as a pretty graph.
589    # this code is currently broken.
590    def outputDot(self, dot):
591        label = "{%s|" % self.path
592        if isSimObject(self.realtype):
593            label +=  '%s|' % self.type
594
595        if self.children:
596            # instantiate children in same order they were added for
597            # backward compatibility (else we can end up with cpu1
598            # before cpu0).
599            for c in self.children:
600                dot.add_edge(pydot.Edge(self.path,c.path, style="bold"))
601
602        simobjs = []
603        for param in self.params:
604            try:
605                if param.value is None:
606                    raise AttributeError, 'Parameter with no value'
607
608                value = param.value
609                string = param.string(value)
610            except Exception, e:
611                msg = 'exception in %s:%s\n%s' % (self.name, param.name, e)
612                e.args = (msg, )
613                raise
614
615            if isSimObject(param.ptype) and string != "Null":
616                simobjs.append(string)
617            else:
618                label += '%s = %s\\n' % (param.name, string)
619
620        for so in simobjs:
621            label += "|<%s> %s" % (so, so)
622            dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so,
623                                    tailport="w"))
624        label += '}'
625        dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label))
626
627        # recursively dump out children
628        for c in self.children:
629            c.outputDot(dot)
630
631class ParamContext(SimObject):
632    pass
633
634#####################################################################
635#
636# Proxy object support.
637#
638#####################################################################
639
640class BaseProxy(object):
641    def __init__(self, search_self, search_up):
642        self._search_self = search_self
643        self._search_up = search_up
644        self._multiplier = None
645
646    def __setattr__(self, attr, value):
647        if not attr.startswith('_'):
648            raise AttributeError, 'cannot set attribute on proxy object'
649        super(BaseProxy, self).__setattr__(attr, value)
650
651    # support multiplying proxies by constants
652    def __mul__(self, other):
653        if not isinstance(other, (int, long, float)):
654            raise TypeError, "Proxy multiplier must be integer"
655        if self._multiplier == None:
656            self._multiplier = other
657        else:
658            # support chained multipliers
659            self._multiplier *= other
660        return self
661
662    __rmul__ = __mul__
663
664    def _mulcheck(self, result):
665        if self._multiplier == None:
666            return result
667        return result * self._multiplier
668
669    def unproxy(self, base):
670        obj = base
671        done = False
672
673        if self._search_self:
674            result, done = self.find(obj)
675
676        if self._search_up:
677            while not done:
678                try: obj = obj._parent
679                except: break
680
681                result, done = self.find(obj)
682
683        if not done:
684            raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \
685                  (self.path(), base.path())
686
687        if isinstance(result, BaseProxy):
688            if result == self:
689                raise RuntimeError, "Cycle in unproxy"
690            result = result.unproxy(obj)
691
692        return self._mulcheck(result)
693
694    def getindex(obj, index):
695        if index == None:
696            return obj
697        try:
698            obj = obj[index]
699        except TypeError:
700            if index != 0:
701                raise
702            # if index is 0 and item is not subscriptable, just
703            # use item itself (so cpu[0] works on uniprocessors)
704        return obj
705    getindex = staticmethod(getindex)
706
707    def set_param_desc(self, pdesc):
708        self._pdesc = pdesc
709
710class AttrProxy(BaseProxy):
711    def __init__(self, search_self, search_up, attr):
712        super(AttrProxy, self).__init__(search_self, search_up)
713        self._attr = attr
714        self._modifiers = []
715
716    def __getattr__(self, attr):
717        # python uses __bases__ internally for inheritance
718        if attr.startswith('_'):
719            return super(AttrProxy, self).__getattr__(self, attr)
720        if hasattr(self, '_pdesc'):
721            raise AttributeError, "Attribute reference on bound proxy"
722        self._modifiers.append(attr)
723        return self
724
725    # support indexing on proxies (e.g., Self.cpu[0])
726    def __getitem__(self, key):
727        if not isinstance(key, int):
728            raise TypeError, "Proxy object requires integer index"
729        self._modifiers.append(key)
730        return self
731
732    def find(self, obj):
733        try:
734            val = getattr(obj, self._attr)
735        except:
736            return None, False
737        while isproxy(val):
738            val = val.unproxy(obj)
739        for m in self._modifiers:
740            if isinstance(m, str):
741                val = getattr(val, m)
742            elif isinstance(m, int):
743                val = val[m]
744            else:
745                assert("Item must be string or integer")
746            while isproxy(val):
747                val = val.unproxy(obj)
748        return val, True
749
750    def path(self):
751        p = self._attr
752        for m in self._modifiers:
753            if isinstance(m, str):
754                p += '.%s' % m
755            elif isinstance(m, int):
756                p += '[%d]' % m
757            else:
758                assert("Item must be string or integer")
759        return p
760
761class AnyProxy(BaseProxy):
762    def find(self, obj):
763        return obj.find_any(self._pdesc.ptype)
764
765    def path(self):
766        return 'any'
767
768def isproxy(obj):
769    if isinstance(obj, (BaseProxy, EthernetAddr)):
770        return True
771    elif isinstance(obj, (list, tuple)):
772        for v in obj:
773            if isproxy(v):
774                return True
775    return False
776
777class ProxyFactory(object):
778    def __init__(self, search_self, search_up):
779        self.search_self = search_self
780        self.search_up = search_up
781
782    def __getattr__(self, attr):
783        if attr == 'any':
784            return AnyProxy(self.search_self, self.search_up)
785        else:
786            return AttrProxy(self.search_self, self.search_up, attr)
787
788# global objects for handling proxies
789Parent = ProxyFactory(search_self = False, search_up = True)
790Self = ProxyFactory(search_self = True, search_up = False)
791
792#####################################################################
793#
794# Parameter description classes
795#
796# The _params dictionary in each class maps parameter names to
797# either a Param or a VectorParam object.  These objects contain the
798# parameter description string, the parameter type, and the default
799# value (loaded from the PARAM section of the .odesc files).  The
800# _convert() method on these objects is used to force whatever value
801# is assigned to the parameter to the appropriate type.
802#
803# Note that the default values are loaded into the class's attribute
804# space when the parameter dictionary is initialized (in
805# MetaConfigNode._setparams()); after that point they aren't used.
806#
807#####################################################################
808
809# Dummy base class to identify types that are legitimate for SimObject
810# parameters.
811class ParamValue(object):
812
813    # default for printing to .ini file is regular string conversion.
814    # will be overridden in some cases
815    def ini_str(self):
816        return str(self)
817
818    # allows us to blithely call unproxy() on things without checking
819    # if they're really proxies or not
820    def unproxy(self, base):
821        return self
822
823# Regular parameter description.
824class ParamDesc(object):
825    def __init__(self, ptype_str, ptype, *args, **kwargs):
826        self.ptype_str = ptype_str
827        # remember ptype only if it is provided
828        if ptype != None:
829            self.ptype = ptype
830
831        if args:
832            if len(args) == 1:
833                self.desc = args[0]
834            elif len(args) == 2:
835                self.default = args[0]
836                self.desc = args[1]
837            else:
838                raise TypeError, 'too many arguments'
839
840        if kwargs.has_key('desc'):
841            assert(not hasattr(self, 'desc'))
842            self.desc = kwargs['desc']
843            del kwargs['desc']
844
845        if kwargs.has_key('default'):
846            assert(not hasattr(self, 'default'))
847            self.default = kwargs['default']
848            del kwargs['default']
849
850        if kwargs:
851            raise TypeError, 'extra unknown kwargs %s' % kwargs
852
853        if not hasattr(self, 'desc'):
854            raise TypeError, 'desc attribute missing'
855
856    def __getattr__(self, attr):
857        if attr == 'ptype':
858            try:
859                ptype = eval(self.ptype_str, m5.objects.__dict__)
860                if not isinstance(ptype, type):
861                    panic("Param qualifier is not a type: %s" % self.ptype)
862                self.ptype = ptype
863                return ptype
864            except NameError:
865                pass
866        raise AttributeError, "'%s' object has no attribute '%s'" % \
867              (type(self).__name__, attr)
868
869    def convert(self, value):
870        if isinstance(value, BaseProxy):
871            value.set_param_desc(self)
872            return value
873        if not hasattr(self, 'ptype') and isNullPointer(value):
874            # deferred evaluation of SimObject; continue to defer if
875            # we're just assigning a null pointer
876            return value
877        if isinstance(value, self.ptype):
878            return value
879        if isNullPointer(value) and issubclass(self.ptype, SimObject):
880            return value
881        return self.ptype(value)
882
883# Vector-valued parameter description.  Just like ParamDesc, except
884# that the value is a vector (list) of the specified type instead of a
885# single value.
886
887class VectorParamValue(list):
888    def ini_str(self):
889        return ' '.join([v.ini_str() for v in self])
890
891    def unproxy(self, base):
892        return [v.unproxy(base) for v in self]
893
894class SimObjVector(VectorParamValue):
895    def print_ini(self):
896        for v in self:
897            v.print_ini()
898
899class VectorParamDesc(ParamDesc):
900    # Convert assigned value to appropriate type.  If the RHS is not a
901    # list or tuple, it generates a single-element list.
902    def convert(self, value):
903        if isinstance(value, (list, tuple)):
904            # list: coerce each element into new list
905            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
906            if isSimObjectSequence(tmp_list):
907                return SimObjVector(tmp_list)
908            else:
909                return VectorParamValue(tmp_list)
910        else:
911            # singleton: leave it be (could coerce to a single-element
912            # list here, but for some historical reason we don't...
913            return ParamDesc.convert(self, value)
914
915
916class ParamFactory(object):
917    def __init__(self, param_desc_class, ptype_str = None):
918        self.param_desc_class = param_desc_class
919        self.ptype_str = ptype_str
920
921    def __getattr__(self, attr):
922        if self.ptype_str:
923            attr = self.ptype_str + '.' + attr
924        return ParamFactory(self.param_desc_class, attr)
925
926    # E.g., Param.Int(5, "number of widgets")
927    def __call__(self, *args, **kwargs):
928        caller_frame = inspect.currentframe().f_back
929        ptype = None
930        try:
931            ptype = eval(self.ptype_str,
932                         caller_frame.f_globals, caller_frame.f_locals)
933            if not isinstance(ptype, type):
934                raise TypeError, \
935                      "Param qualifier is not a type: %s" % ptype
936        except NameError:
937            # if name isn't defined yet, assume it's a SimObject, and
938            # try to resolve it later
939            pass
940        return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs)
941
942Param = ParamFactory(ParamDesc)
943VectorParam = ParamFactory(VectorParamDesc)
944
945#####################################################################
946#
947# Parameter Types
948#
949# Though native Python types could be used to specify parameter types
950# (the 'ptype' field of the Param and VectorParam classes), it's more
951# flexible to define our own set of types.  This gives us more control
952# over how Python expressions are converted to values (via the
953# __init__() constructor) and how these values are printed out (via
954# the __str__() conversion method).  Eventually we'll need these types
955# to correspond to distinct C++ types as well.
956#
957#####################################################################
958
959# superclass for "numeric" parameter values, to emulate math
960# operations in a type-safe way.  e.g., a Latency times an int returns
961# a new Latency object.
962class NumericParamValue(ParamValue):
963    def __str__(self):
964        return str(self.value)
965
966    def __float__(self):
967        return float(self.value)
968
969    # hook for bounds checking
970    def _check(self):
971        return
972
973    def __mul__(self, other):
974        newobj = self.__class__(self)
975        newobj.value *= other
976        newobj._check()
977        return newobj
978
979    __rmul__ = __mul__
980
981    def __div__(self, other):
982        newobj = self.__class__(self)
983        newobj.value /= other
984        newobj._check()
985        return newobj
986
987    def __sub__(self, other):
988        newobj = self.__class__(self)
989        newobj.value -= other
990        newobj._check()
991        return newobj
992
993class Range(ParamValue):
994    type = int # default; can be overridden in subclasses
995    def __init__(self, *args, **kwargs):
996
997        def handle_kwargs(self, kwargs):
998            if 'end' in kwargs:
999                self.second = self.type(kwargs.pop('end'))
1000            elif 'size' in kwargs:
1001                self.second = self.first + self.type(kwargs.pop('size')) - 1
1002            else:
1003                raise TypeError, "Either end or size must be specified"
1004
1005        if len(args) == 0:
1006            self.first = self.type(kwargs.pop('start'))
1007            handle_kwargs(self, kwargs)
1008
1009        elif len(args) == 1:
1010            if kwargs:
1011                self.first = self.type(args[0])
1012                handle_kwargs(self, kwargs)
1013            elif isinstance(args[0], Range):
1014                self.first = self.type(args[0].first)
1015                self.second = self.type(args[0].second)
1016            else:
1017                self.first = self.type(0)
1018                self.second = self.type(args[0]) - 1
1019
1020        elif len(args) == 2:
1021            self.first = self.type(args[0])
1022            self.second = self.type(args[1])
1023        else:
1024            raise TypeError, "Too many arguments specified"
1025
1026        if kwargs:
1027            raise TypeError, "too many keywords: %s" % kwargs.keys()
1028
1029    def __str__(self):
1030        return '%s:%s' % (self.first, self.second)
1031
1032# Metaclass for bounds-checked integer parameters.  See CheckedInt.
1033class CheckedIntType(type):
1034    def __init__(cls, name, bases, dict):
1035        super(CheckedIntType, cls).__init__(name, bases, dict)
1036
1037        # CheckedInt is an abstract base class, so we actually don't
1038        # want to do any processing on it... the rest of this code is
1039        # just for classes that derive from CheckedInt.
1040        if name == 'CheckedInt':
1041            return
1042
1043        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
1044            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
1045                panic("CheckedInt subclass %s must define either\n" \
1046                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
1047                      % name);
1048            if cls.unsigned:
1049                cls.min = 0
1050                cls.max = 2 ** cls.size - 1
1051            else:
1052                cls.min = -(2 ** (cls.size - 1))
1053                cls.max = (2 ** (cls.size - 1)) - 1
1054
1055# Abstract superclass for bounds-checked integer parameters.  This
1056# class is subclassed to generate parameter classes with specific
1057# bounds.  Initialization of the min and max bounds is done in the
1058# metaclass CheckedIntType.__init__.
1059class CheckedInt(NumericParamValue):
1060    __metaclass__ = CheckedIntType
1061
1062    def _check(self):
1063        if not self.min <= self.value <= self.max:
1064            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
1065                  (self.min, self.value, self.max)
1066
1067    def __init__(self, value):
1068        if isinstance(value, str):
1069            self.value = toInteger(value)
1070        elif isinstance(value, (int, long, float)):
1071            self.value = long(value)
1072        self._check()
1073
1074class Int(CheckedInt):      size = 32; unsigned = False
1075class Unsigned(CheckedInt): size = 32; unsigned = True
1076
1077class Int8(CheckedInt):     size =  8; unsigned = False
1078class UInt8(CheckedInt):    size =  8; unsigned = True
1079class Int16(CheckedInt):    size = 16; unsigned = False
1080class UInt16(CheckedInt):   size = 16; unsigned = True
1081class Int32(CheckedInt):    size = 32; unsigned = False
1082class UInt32(CheckedInt):   size = 32; unsigned = True
1083class Int64(CheckedInt):    size = 64; unsigned = False
1084class UInt64(CheckedInt):   size = 64; unsigned = True
1085
1086class Counter(CheckedInt):  size = 64; unsigned = True
1087class Tick(CheckedInt):     size = 64; unsigned = True
1088class TcpPort(CheckedInt):  size = 16; unsigned = True
1089class UdpPort(CheckedInt):  size = 16; unsigned = True
1090
1091class Percent(CheckedInt):  min = 0; max = 100
1092
1093class Float(ParamValue, float):
1094    pass
1095
1096class MemorySize(CheckedInt):
1097    size = 64
1098    unsigned = True
1099    def __init__(self, value):
1100        if isinstance(value, MemorySize):
1101            self.value = value.value
1102        else:
1103            self.value = toMemorySize(value)
1104        self._check()
1105
1106class MemorySize32(CheckedInt):
1107    size = 32
1108    unsigned = True
1109    def __init__(self, value):
1110        if isinstance(value, MemorySize):
1111            self.value = value.value
1112        else:
1113            self.value = toMemorySize(value)
1114        self._check()
1115
1116class Addr(CheckedInt):
1117    size = 64
1118    unsigned = True
1119    def __init__(self, value):
1120        if isinstance(value, Addr):
1121            self.value = value.value
1122        else:
1123            try:
1124                self.value = toMemorySize(value)
1125            except TypeError:
1126                self.value = long(value)
1127        self._check()
1128
1129class AddrRange(Range):
1130    type = Addr
1131
1132# String-valued parameter.  Just mixin the ParamValue class
1133# with the built-in str class.
1134class String(ParamValue,str):
1135    pass
1136
1137# Boolean parameter type.  Python doesn't let you subclass bool, since
1138# it doesn't want to let you create multiple instances of True and
1139# False.  Thus this is a little more complicated than String.
1140class Bool(ParamValue):
1141    def __init__(self, value):
1142        try:
1143            self.value = toBool(value)
1144        except TypeError:
1145            self.value = bool(value)
1146
1147    def __str__(self):
1148        return str(self.value)
1149
1150    def ini_str(self):
1151        if self.value:
1152            return 'true'
1153        return 'false'
1154
1155def IncEthernetAddr(addr, val = 1):
1156    bytes = map(lambda x: int(x, 16), addr.split(':'))
1157    bytes[5] += val
1158    for i in (5, 4, 3, 2, 1):
1159        val,rem = divmod(bytes[i], 256)
1160        bytes[i] = rem
1161        if val == 0:
1162            break
1163        bytes[i - 1] += val
1164    assert(bytes[0] <= 255)
1165    return ':'.join(map(lambda x: '%02x' % x, bytes))
1166
1167class NextEthernetAddr(object):
1168    addr = "00:90:00:00:00:01"
1169
1170    def __init__(self, inc = 1):
1171        self.value = NextEthernetAddr.addr
1172        NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc)
1173
1174class EthernetAddr(ParamValue):
1175    def __init__(self, value):
1176        if value == NextEthernetAddr:
1177            self.value = value
1178            return
1179
1180        if not isinstance(value, str):
1181            raise TypeError, "expected an ethernet address and didn't get one"
1182
1183        bytes = value.split(':')
1184        if len(bytes) != 6:
1185            raise TypeError, 'invalid ethernet address %s' % value
1186
1187        for byte in bytes:
1188            if not 0 <= int(byte) <= 256:
1189                raise TypeError, 'invalid ethernet address %s' % value
1190
1191        self.value = value
1192
1193    def unproxy(self, base):
1194        if self.value == NextEthernetAddr:
1195            self.addr = self.value().value
1196        return self
1197
1198    def __str__(self):
1199        if self.value == NextEthernetAddr:
1200            if hasattr(self, 'addr'):
1201                return self.addr
1202            else:
1203                return "NextEthernetAddr (unresolved)"
1204        else:
1205            return self.value
1206
1207# Special class for NULL pointers.  Note the special check in
1208# make_param_value() above that lets these be assigned where a
1209# SimObject is required.
1210# only one copy of a particular node
1211class NullSimObject(object):
1212    __metaclass__ = Singleton
1213
1214    def __call__(cls):
1215        return cls
1216
1217    def _instantiate(self, parent = None, path = ''):
1218        pass
1219
1220    def ini_str(self):
1221        return 'Null'
1222
1223    def unproxy(self, base):
1224        return self
1225
1226    def set_path(self, parent, name):
1227        pass
1228    def __str__(self):
1229        return 'Null'
1230
1231# The only instance you'll ever need...
1232Null = NULL = NullSimObject()
1233
1234# Enumerated types are a little more complex.  The user specifies the
1235# type as Enum(foo) where foo is either a list or dictionary of
1236# alternatives (typically strings, but not necessarily so).  (In the
1237# long run, the integer value of the parameter will be the list index
1238# or the corresponding dictionary value.  For now, since we only check
1239# that the alternative is valid and then spit it into a .ini file,
1240# there's not much point in using the dictionary.)
1241
1242# What Enum() must do is generate a new type encapsulating the
1243# provided list/dictionary so that specific values of the parameter
1244# can be instances of that type.  We define two hidden internal
1245# classes (_ListEnum and _DictEnum) to serve as base classes, then
1246# derive the new type from the appropriate base class on the fly.
1247
1248
1249# Metaclass for Enum types
1250class MetaEnum(type):
1251    def __init__(cls, name, bases, init_dict):
1252        if init_dict.has_key('map'):
1253            if not isinstance(cls.map, dict):
1254                raise TypeError, "Enum-derived class attribute 'map' " \
1255                      "must be of type dict"
1256            # build list of value strings from map
1257            cls.vals = cls.map.keys()
1258            cls.vals.sort()
1259        elif init_dict.has_key('vals'):
1260            if not isinstance(cls.vals, list):
1261                raise TypeError, "Enum-derived class attribute 'vals' " \
1262                      "must be of type list"
1263            # build string->value map from vals sequence
1264            cls.map = {}
1265            for idx,val in enumerate(cls.vals):
1266                cls.map[val] = idx
1267        else:
1268            raise TypeError, "Enum-derived class must define "\
1269                  "attribute 'map' or 'vals'"
1270
1271        super(MetaEnum, cls).__init__(name, bases, init_dict)
1272
1273    def cpp_declare(cls):
1274        s = 'enum %s {\n    ' % cls.__name__
1275        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
1276        s += '\n};\n'
1277        return s
1278
1279# Base class for enum types.
1280class Enum(ParamValue):
1281    __metaclass__ = MetaEnum
1282    vals = []
1283
1284    def __init__(self, value):
1285        if value not in self.map:
1286            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1287                  % (value, self.vals)
1288        self.value = value
1289
1290    def __str__(self):
1291        return self.value
1292
1293ticks_per_sec = None
1294
1295# how big does a rounding error need to be before we warn about it?
1296frequency_tolerance = 0.001  # 0.1%
1297
1298# convert a floting-point # of ticks to integer, and warn if rounding
1299# discards too much precision
1300def tick_check(float_ticks):
1301    if float_ticks == 0:
1302        return 0
1303    int_ticks = int(round(float_ticks))
1304    err = (float_ticks - int_ticks) / float_ticks
1305    if err > frequency_tolerance:
1306        print >> sys.stderr, "Warning: rounding error > tolerance"
1307        print >> sys.stderr, "    %f rounded to %d" % (float_ticks, int_ticks)
1308        #raise ValueError
1309    return int_ticks
1310
1311def getLatency(value):
1312    if isinstance(value, Latency) or isinstance(value, Clock):
1313        return value.value
1314    elif isinstance(value, Frequency) or isinstance(value, RootClock):
1315        return 1 / value.value
1316    elif isinstance(value, str):
1317        try:
1318            return toLatency(value)
1319        except ValueError:
1320            try:
1321                return 1 / toFrequency(value)
1322            except ValueError:
1323                pass # fall through
1324    raise ValueError, "Invalid Frequency/Latency value '%s'" % value
1325
1326
1327class Latency(NumericParamValue):
1328    def __init__(self, value):
1329        self.value = getLatency(value)
1330
1331    def __getattr__(self, attr):
1332        if attr in ('latency', 'period'):
1333            return self
1334        if attr == 'frequency':
1335            return Frequency(self)
1336        raise AttributeError, "Latency object has no attribute '%s'" % attr
1337
1338    # convert latency to ticks
1339    def ini_str(self):
1340        return str(tick_check(self.value * ticks_per_sec))
1341
1342class Frequency(NumericParamValue):
1343    def __init__(self, value):
1344        self.value = 1 / getLatency(value)
1345
1346    def __getattr__(self, attr):
1347        if attr == 'frequency':
1348            return self
1349        if attr in ('latency', 'period'):
1350            return Latency(self)
1351        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1352
1353    # convert frequency to ticks per period
1354    def ini_str(self):
1355        return self.period.ini_str()
1356
1357# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz).
1358# We can't inherit from Frequency because we don't want it to be directly
1359# assignable to a regular Frequency parameter.
1360class RootClock(ParamValue):
1361    def __init__(self, value):
1362        self.value = 1 / getLatency(value)
1363
1364    def __getattr__(self, attr):
1365        if attr == 'frequency':
1366            return Frequency(self)
1367        if attr in ('latency', 'period'):
1368            return Latency(self)
1369        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1370
1371    def ini_str(self):
1372        return str(tick_check(self.value))
1373
1374# A generic frequency and/or Latency value.  Value is stored as a latency,
1375# but to avoid ambiguity this object does not support numeric ops (* or /).
1376# An explicit conversion to a Latency or Frequency must be made first.
1377class Clock(ParamValue):
1378    def __init__(self, value):
1379        self.value = getLatency(value)
1380
1381    def __getattr__(self, attr):
1382        if attr == 'frequency':
1383            return Frequency(self)
1384        if attr in ('latency', 'period'):
1385            return Latency(self)
1386        raise AttributeError, "Frequency object has no attribute '%s'" % attr
1387
1388    def ini_str(self):
1389        return self.period.ini_str()
1390
1391class NetworkBandwidth(float,ParamValue):
1392    def __new__(cls, value):
1393        val = toNetworkBandwidth(value) / 8.0
1394        return super(cls, NetworkBandwidth).__new__(cls, val)
1395
1396    def __str__(self):
1397        return str(self.val)
1398
1399    def ini_str(self):
1400        return '%f' % (ticks_per_sec / float(self))
1401
1402class MemoryBandwidth(float,ParamValue):
1403    def __new__(self, value):
1404        val = toMemoryBandwidth(value)
1405        return super(cls, MemoryBandwidth).__new__(cls, val)
1406
1407    def __str__(self):
1408        return str(self.val)
1409
1410    def ini_str(self):
1411        return '%f' % (ticks_per_sec / float(self))
1412
1413#
1414# "Constants"... handy aliases for various values.
1415#
1416
1417# Some memory range specifications use this as a default upper bound.
1418MaxAddr = Addr.max
1419MaxTick = Tick.max
1420AllMemory = AddrRange(0, MaxAddr)
1421
1422#####################################################################
1423
1424# __all__ defines the list of symbols that get exported when
1425# 'from config import *' is invoked.  Try to keep this reasonably
1426# short to avoid polluting other namespaces.
1427__all__ = ['SimObject', 'ParamContext', 'Param', 'VectorParam',
1428           'Parent', 'Self',
1429           'Enum', 'Bool', 'String', 'Float',
1430           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1431           'Int32', 'UInt32', 'Int64', 'UInt64',
1432           'Counter', 'Addr', 'Tick', 'Percent',
1433           'TcpPort', 'UdpPort', 'EthernetAddr',
1434           'MemorySize', 'MemorySize32',
1435           'Latency', 'Frequency', 'RootClock', 'Clock',
1436           'NetworkBandwidth', 'MemoryBandwidth',
1437           'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory',
1438           'Null', 'NULL',
1439           'NextEthernetAddr']
1440
1441