SimObject.py revision 702
1from __future__ import generators
2
3import os
4import re
5import sys
6
7#####################################################################
8#
9# M5 Python Configuration Utility
10#
11# The basic idea is to write simple Python programs that build Python
12# objects corresponding to M5 SimObjects for the deisred simulation
13# configuration.  For now, the Python emits a .ini file that can be
14# parsed by M5.  In the future, some tighter integration between M5
15# and the Python interpreter may allow bypassing the .ini file.
16#
17# Each SimObject class in M5 is represented by a Python class with the
18# same name.  The Python inheritance tree mirrors the M5 C++ tree
19# (e.g., SimpleCPU derives from BaseCPU in both cases, and all
20# SimObjects inherit from a single SimObject base class).  To specify
21# an instance of an M5 SimObject in a configuration, the user simply
22# instantiates the corresponding Python object.  The parameters for
23# that SimObject are given by assigning to attributes of the Python
24# object, either using keyword assignment in the constructor or in
25# separate assignment statements.  For example:
26#
27# cache = BaseCache('my_cache', root, size=64*K)
28# cache.hit_latency = 3
29# cache.assoc = 8
30#
31# (The first two constructor arguments specify the name of the created
32# cache and its parent node in the hierarchy.)
33#
34# The magic lies in the mapping of the Python attributes for SimObject
35# classes to the actual SimObject parameter specifications.  This
36# allows parameter validity checking in the Python code.  Continuing
37# the example above, the statements "cache.blurfl=3" or
38# "cache.assoc='hello'" would both result in runtime errors in Python,
39# since the BaseCache object has no 'blurfl' parameter and the 'assoc'
40# parameter requires an integer, respectively.  This magic is done
41# primarily by overriding the special __setattr__ method that controls
42# assignment to object attributes.
43#
44# The Python module provides another class, ConfigNode, which is a
45# superclass of SimObject.  ConfigNode implements the parent/child
46# relationship for building the configuration hierarchy tree.
47# Concrete instances of ConfigNode can be used to group objects in the
48# hierarchy, but do not correspond to SimObjects themselves (like a
49# .ini section with "children=" but no "type=".
50#
51# Once a set of Python objects have been instantiated in a hierarchy,
52# calling 'instantiate(obj)' (where obj is the root of the hierarchy)
53# will generate a .ini file.  See simple-4cpu.py for an example
54# (corresponding to m5-test/simple-4cpu.ini).
55#
56#####################################################################
57
58#####################################################################
59#
60# ConfigNode/SimObject classes
61#
62# The Python class hierarchy rooted by ConfigNode (which is the base
63# class of SimObject, which in turn is the base class of all other M5
64# SimObject classes) has special attribute behavior.  In general, an
65# object in this hierarchy has three categories of attribute-like
66# things:
67#
68# 1. Regular Python methods and variables.  These must start with an
69# underscore to be treated normally.
70#
71# 2. SimObject parameters.  These values are stored as normal Python
72# attributes, but all assignments to these attributes are checked
73# against the pre-defined set of parameters stored in the class's
74# _param_dict dictionary.  Assignments to attributes that do not
75# correspond to predefined parameters, or that are not of the correct
76# type, incur runtime errors.
77#
78# 3. Hierarchy children.  The child nodes of a ConfigNode are stored
79# in the node's _children dictionary, but can be accessed using the
80# Python attribute dot-notation (just as they are printed out by the
81# simulator).  Children cannot be created using attribute assigment;
82# they must be added by specifying the parent node in the child's
83# constructor or using the '+=' operator.
84
85# The SimObject parameters are the most complex, for a few reasons.
86# First, both parameter descriptions and parameter values are
87# inherited.  Thus parameter description lookup must go up the
88# inheritance chain like normal attribute lookup, but this behavior
89# must be explicitly coded since the lookup occurs in each class's
90# _param_dict attribute.  Second, because parameter values can be set
91# on SimObject classes (to implement default values), the parameter
92# checking behavior must be enforced on class attribute assignments as
93# well as instance attribute assignments.  Finally, because we allow
94# class specialization via inheritance (e.g., see the L1Cache class in
95# the simple-4cpu.py example), we must do parameter checking even on
96# class instantiation.  To provide all these features, we use a
97# metaclass to define most of the SimObject parameter behavior for
98# this class hierarchy.
99#
100#####################################################################
101
102# The metaclass for ConfigNode (and thus for everything that derives
103# from ConfigNode, including SimObject).  This class controls how new
104# classes that derive from ConfigNode are instantiated, and provides
105# inherited class behavior (just like a class controls how instances
106# of that class are instantiated, and provides inherited instance
107# behavior).
108class MetaConfigNode(type):
109
110    # __new__ is called before __init__, and is where the statements
111    # in the body of the class definition get loaded into the class's
112    # __dict__.  We intercept this to filter out parameter assignments
113    # and only allow "private" attributes to be passed to the base
114    # __new__ (starting with underscore).
115    def __new__(cls, name, bases, dict):
116        priv_keys = [k for k in dict.iterkeys() if k.startswith('_')]
117        priv_dict = {}
118        for k in priv_keys: priv_dict[k] = dict[k]; del dict[k]
119        # entries left in dict will get passed to __init__, where we'll
120        # deal with them as params.
121        return super(MetaConfigNode, cls).__new__(cls, name, bases, priv_dict)
122
123    # initialization: start out with an empty param dict (makes life
124    # simpler if we can assume _param_dict is always valid).  Also
125    # build inheritance list to simplify searching for inherited
126    # params.  Finally set parameters specified in class definition
127    # (if any).
128    def __init__(cls, name, bases, dict):
129        super(MetaConfigNode, cls).__init__(cls, name, bases, {})
130        # initialize _param_dict to empty
131        cls._param_dict = {}
132        # __mro__ is the ordered list of classes Python uses for
133        # method resolution.  We want to pick out the ones that have a
134        # _param_dict attribute for doing parameter lookups.
135        cls._param_bases = \
136                         [c for c in cls.__mro__ if hasattr(c, '_param_dict')]
137        # initialize attributes with values from class definition
138        for (pname, value) in dict.items():
139            try:
140                setattr(cls, pname, value)
141            except Exception, exc:
142                print "Error setting '%s' to '%s' on class '%s'\n" \
143                      % (pname, value, cls.__name__), exc
144
145    # set the class's parameter dictionary (called when loading
146    # class descriptions)
147    def set_param_dict(cls, param_dict):
148        # should only be called once (current one should be empty one
149        # from __init__)
150        assert not cls._param_dict
151        cls._param_dict = param_dict
152        # initialize attributes with default values
153        for (pname, param) in param_dict.items():
154            try:
155                setattr(cls, pname, param.default)
156            except Exception, exc:
157                print "Error setting '%s' default on class '%s'\n" \
158                      % (pname, cls.__name__), exc
159
160    # Lookup a parameter description by name in the given class.  Use
161    # the _param_bases list defined in __init__ to go up the
162    # inheritance hierarchy if necessary.
163    def lookup_param(cls, param_name):
164        for c in cls._param_bases:
165            param = c._param_dict.get(param_name)
166            if param: return param
167        return None
168
169    # Set attribute (called on foo.attr_name = value when foo is an
170    # instance of class cls).
171    def __setattr__(cls, attr_name, value):
172        # normal processing for private attributes
173        if attr_name.startswith('_'):
174            object.__setattr__(cls, attr_name, value)
175            return
176        # no '_': must be SimObject param
177        param = cls.lookup_param(attr_name)
178        if not param:
179            raise AttributeError, \
180                  "Class %s has no parameter %s" % (cls.__name__, attr_name)
181        # It's ok: set attribute by delegating to 'object' class.
182        # Note the use of param.make_value() to verify/canonicalize
183        # the assigned value
184        object.__setattr__(cls, attr_name, param.make_value(value))
185
186    # generator that iterates across all parameters for this class and
187    # all classes it inherits from
188    def all_param_names(cls):
189        for c in cls._param_bases:
190            for p in c._param_dict.iterkeys():
191                yield p
192
193# The ConfigNode class is the root of the special hierarchy.  Most of
194# the code in this class deals with the configuration hierarchy itself
195# (parent/child node relationships).
196class ConfigNode(object):
197    # Specify metaclass.  Any class inheriting from ConfigNode will
198    # get this metaclass.
199    __metaclass__ = MetaConfigNode
200
201    # Constructor.  Since bare ConfigNodes don't have parameters, just
202    # worry about the name and the parent/child stuff.
203    def __init__(self, _name, _parent=None):
204        # Type-check _name
205        if type(_name) != str:
206            if isinstance(_name, ConfigNode):
207                # special case message for common error of trying to
208                # coerce a SimObject to the wrong type
209                raise TypeError, \
210                      "Attempt to coerce %s to %s" \
211                      % (_name.__class__.__name__, self.__class__.__name__)
212            else:
213                raise TypeError, \
214                      "%s name must be string (was %s, %s)" \
215                      % (self.__class__.__name__, _name, type(_name))
216        # if specified, parent must be a subclass of ConfigNode
217        if _parent != None and not isinstance(_parent, ConfigNode):
218            raise TypeError, \
219                  "%s parent must be ConfigNode subclass (was %s, %s)" \
220                  % (self.__class__.__name__, _name, type(_name))
221        self._name = _name
222        self._parent = _parent
223        self._children = {}
224        if (_parent):
225            _parent.__addChild(self)
226        # Set up absolute path from root.
227        if (_parent and _parent._path != 'Universe'):
228            self._path = _parent._path + '.' + self._name
229        else:
230            self._path = self._name
231
232    # When printing (e.g. to .ini file), just give the name.
233    def __str__(self):
234        return self._name
235
236    # Catch attribute accesses that could be requesting children, and
237    # satisfy them.  Note that __getattr__ is called only if the
238    # regular attribute lookup fails, so private and parameter lookups
239    # will already be satisfied before we ever get here.
240    def __getattr__(self, name):
241        try:
242            return self._children[name]
243        except KeyError:
244            raise AttributeError, \
245                  "Node '%s' has no attribute or child '%s'" \
246                  % (self._name, name)
247
248    # Set attribute.  All attribute assignments go through here.  Must
249    # be private attribute (starts with '_') or valid parameter entry.
250    # Basically identical to MetaConfigClass.__setattr__(), except
251    # this handles instances rather than class attributes.
252    def __setattr__(self, attr_name, value):
253        if attr_name.startswith('_'):
254            object.__setattr__(self, attr_name, value)
255            return
256        # not private; look up as param
257        param = self.__class__.lookup_param(attr_name)
258        if not param:
259            raise AttributeError, \
260                  "Class %s has no parameter %s" \
261                  % (self.__class__.__name__, attr_name)
262        # It's ok: set attribute by delegating to 'object' class.
263        # Note the use of param.make_value() to verify/canonicalize
264        # the assigned value
265        object.__setattr__(self, attr_name, param.make_value(value))
266
267    # Add a child to this node.
268    def __addChild(self, new_child):
269        # set child's parent before calling this function
270        assert new_child._parent == self
271        if not isinstance(new_child, ConfigNode):
272            raise TypeError, \
273                  "ConfigNode child must also be of class ConfigNode"
274        if new_child._name in self._children:
275            raise AttributeError, \
276                  "Node '%s' already has a child '%s'" \
277                  % (self._name, new_child._name)
278        self._children[new_child._name] = new_child
279
280    # operator overload for '+='.  You can say "node += child" to add
281    # a child that was created with parent=None.  An early attempt
282    # at playing with syntax; turns out not to be that useful.
283    def __iadd__(self, new_child):
284        if new_child._parent != None:
285            raise AttributeError, \
286                  "Node '%s' already has a parent" % new_child._name
287        new_child._parent = self
288        self.__addChild(new_child)
289        return self
290
291    # Print instance info to .ini file.
292    def _instantiate(self):
293        print '[' + self._path + ']'	# .ini section header
294        if self._children:
295            # instantiate children in sorted order for backward
296            # compatibility (else we can end up with cpu1 before cpu0).
297            child_names = self._children.keys()
298            child_names.sort()
299            print 'children =',
300            for child_name in child_names:
301                print child_name,
302            print
303        self._instantiateParams()
304        print
305        # recursively dump out children
306        if self._children:
307            for child_name in child_names:
308                self._children[child_name]._instantiate()
309
310    # ConfigNodes have no parameters.  Overridden by SimObject.
311    def _instantiateParams(self):
312        pass
313
314# SimObject is a minimal extension of ConfigNode, implementing a
315# hierarchy node that corresponds to an M5 SimObject.  It prints out a
316# "type=" line to indicate its SimObject class, prints out the
317# assigned parameters corresponding to its class, and allows
318# parameters to be set by keyword in the constructor.  Note that most
319# of the heavy lifting for the SimObject param handling is done in the
320# MetaConfigNode metaclass.
321
322class SimObject(ConfigNode):
323    # initialization: like ConfigNode, but handle keyword-based
324    # parameter initializers.
325    def __init__(self, _name, _parent=None, **params):
326        ConfigNode.__init__(self, _name, _parent)
327        for param, value in params.items():
328            setattr(self, param, value)
329
330    # print type and parameter values to .ini file
331    def _instantiateParams(self):
332        print "type =", self.__class__._name
333        for pname in self.__class__.all_param_names():
334            value = getattr(self, pname)
335            if value != None:
336                print pname, '=', value
337
338    def _sim_code(cls):
339        name = cls.__name__
340        param_names = cls._param_dict.keys()
341        param_names.sort()
342        code = "BEGIN_DECLARE_SIM_OBJECT_PARAMS(%s)\n" % name
343        decls = ["  " + cls._param_dict[pname].sim_decl(pname) \
344                 for pname in param_names]
345        code += "\n".join(decls) + "\n"
346        code += "END_DECLARE_SIM_OBJECT_PARAMS(%s)\n\n" % name
347        code += "BEGIN_INIT_SIM_OBJECT_PARAMS(%s)\n" % name
348        inits = ["  " + cls._param_dict[pname].sim_init(pname) \
349                 for pname in param_names]
350        code += ",\n".join(inits) + "\n"
351        code += "END_INIT_SIM_OBJECT_PARAMS(%s)\n\n" % name
352        return code
353    _sim_code = classmethod(_sim_code)
354
355#####################################################################
356#
357# Parameter description classes
358#
359# The _param_dict dictionary in each class maps parameter names to
360# either a Param or a VectorParam object.  These objects contain the
361# parameter description string, the parameter type, and the default
362# value (loaded from the PARAM section of the .odesc files).  The
363# make_value() method on these objects is used to force whatever value
364# is assigned to the parameter to the appropriate type.
365#
366# Note that the default values are loaded into the class's attribute
367# space when the parameter dictionary is initialized (in
368# MetaConfigNode.set_param_dict()); after that point they aren't
369# used.
370#
371#####################################################################
372
373def isNullPointer(value):
374    return isinstance(value, NullSimObject)
375
376def isSimObjectType(ptype):
377    return issubclass(ptype, SimObject)
378
379# Regular parameter.
380class Param(object):
381    # Constructor.  E.g., Param(Int, "number of widgets", 5)
382    def __init__(self, ptype, desc, default=None):
383        self.ptype = ptype
384        self.ptype_name = self.ptype.__name__
385        self.desc = desc
386        self.default = default
387
388    # Convert assigned value to appropriate type.  Force parameter
389    # value (rhs of '=') to ptype (or None, which means not set).
390    def make_value(self, value):
391        # nothing to do if None or already correct type.  Also allow NULL
392        # pointer to be assigned where a SimObject is expected.
393        if value == None or isinstance(value, self.ptype) or \
394               isNullPointer(value) and isSimObjectType(self.ptype):
395            return value
396        # this type conversion will raise an exception if it's illegal
397        return self.ptype(value)
398
399    def sim_decl(self, name):
400        return 'Param<%s> %s;' % (self.ptype_name, name)
401
402    def sim_init(self, name):
403        if self.default == None:
404            return 'INIT_PARAM(%s, "%s")' % (name, self.desc)
405        else:
406            return 'INIT_PARAM_DFLT(%s, "%s", %s)' % \
407                   (name, self.desc, str(self.default))
408
409# The _VectorParamValue class is a wrapper for vector-valued
410# parameters.  The leading underscore indicates that users shouldn't
411# see this class; it's magically generated by VectorParam.  The
412# parameter values are stored in the 'value' field as a Python list of
413# whatever type the parameter is supposed to be.  The only purpose of
414# storing these instead of a raw Python list is that we can override
415# the __str__() method to not print out '[' and ']' in the .ini file.
416class _VectorParamValue(object):
417    def __init__(self, list):
418        self.value = list
419
420    def __str__(self):
421        return ' '.join(map(str, self.value))
422
423# Vector-valued parameter description.  Just like Param, except that
424# the value is a vector (list) of the specified type instead of a
425# single value.
426class VectorParam(Param):
427
428    # Inherit Param constructor.  However, the resulting parameter
429    # will be a list of ptype rather than a single element of ptype.
430    def __init__(self, ptype, desc, default=None):
431        Param.__init__(self, ptype, desc, default)
432
433    # Convert assigned value to appropriate type.  If the RHS is not a
434    # list or tuple, it generates a single-element list.
435    def make_value(self, value):
436        if value == None: return value
437        if isinstance(value, list) or isinstance(value, tuple):
438            # list: coerce each element into new list
439            val_list = [Param.make_value(self, v) for v in iter(value)]
440        else:
441            # singleton: coerce & wrap in a list
442            val_list = [Param.make_value(self, value)]
443        # wrap list in _VectorParamValue (see above)
444        return _VectorParamValue(val_list)
445
446    def sim_decl(self, name):
447        return 'VectorParam<%s> %s;' % (self.ptype_name, name)
448
449    # sim_init inherited from Param
450
451#####################################################################
452#
453# Parameter Types
454#
455# Though native Python types could be used to specify parameter types
456# (the 'ptype' field of the Param and VectorParam classes), it's more
457# flexible to define our own set of types.  This gives us more control
458# over how Python expressions are converted to values (via the
459# __init__() constructor) and how these values are printed out (via
460# the __str__() conversion method).  Eventually we'll need these types
461# to correspond to distinct C++ types as well.
462#
463#####################################################################
464
465# Integer parameter type.
466class Int(object):
467    # Constructor.  Value must be Python int or long (long integer).
468    def __init__(self, value):
469        t = type(value)
470        if t == int or t == long:
471            self.value = value
472        else:
473            raise TypeError, "Int param got value %s %s" % (repr(value), t)
474
475    # Use Python string conversion.  Note that this puts an 'L' on the
476    # end of long integers; we can strip that off here if it gives us
477    # trouble.
478    def __str__(self):
479        return str(self.value)
480
481# Counter, Addr, and Tick are just aliases for Int for now.
482class Counter(Int):
483    pass
484
485class Addr(Int):
486    pass
487
488class Tick(Int):
489    pass
490
491# Boolean parameter type.
492class Bool(object):
493
494    # Constructor.  Typically the value will be one of the Python bool
495    # constants True or False (or the aliases true and false below).
496    # Also need to take integer 0 or 1 values since bool was not a
497    # distinct type in Python 2.2.  Parse a bunch of boolean-sounding
498    # strings too just for kicks.
499    def __init__(self, value):
500        t = type(value)
501        if t == bool:
502            self.value = value
503        elif t == int or t == long:
504            if value == 1:
505                self.value = True
506            elif value == 0:
507                self.value = False
508        elif t == str:
509            v = value.lower()
510            if v == "true" or v == "t" or v == "yes" or v == "y":
511                self.value = True
512            elif v == "false" or v == "f" or v == "no" or v == "n":
513                self.value = False
514        # if we didn't set it yet, it must not be something we understand
515        if not hasattr(self, 'value'):
516            raise TypeError, "Bool param got value %s %s" % (repr(value), t)
517
518    # Generate printable string version.
519    def __str__(self):
520        if self.value: return "true"
521        else: return "false"
522
523# String-valued parameter.
524class String(object):
525    # Constructor.  Value must be Python string.
526    def __init__(self, value):
527        t = type(value)
528        if t == str:
529            self.value = value
530        else:
531            raise TypeError, "String param got value %s %s" % (repr(value), t)
532
533    # Generate printable string version.  Not too tricky.
534    def __str__(self):
535        return self.value
536
537# Special class for NULL pointers.  Note the special check in
538# make_param_value() above that lets these be assigned where a
539# SimObject is required.
540class NullSimObject(object):
541    # Constructor.  No parameters, nothing to do.
542    def __init__(self):
543        pass
544
545    def __str__(self):
546        return "NULL"
547
548# The only instance you'll ever need...
549NULL = NullSimObject()
550
551# Enumerated types are a little more complex.  The user specifies the
552# type as Enum(foo) where foo is either a list or dictionary of
553# alternatives (typically strings, but not necessarily so).  (In the
554# long run, the integer value of the parameter will be the list index
555# or the corresponding dictionary value.  For now, since we only check
556# that the alternative is valid and then spit it into a .ini file,
557# there's not much point in using the dictionary.)
558
559# What Enum() must do is generate a new type encapsulating the
560# provided list/dictionary so that specific values of the parameter
561# can be instances of that type.  We define two hidden internal
562# classes (_ListEnum and _DictEnum) to serve as base classes, then
563# derive the new type from the appropriate base class on the fly.
564
565
566# Base class for list-based Enum types.
567class _ListEnum(object):
568    # Constructor.  Value must be a member of the type's map list.
569    def __init__(self, value):
570        if value in self.map:
571            self.value = value
572            self.index = self.map.index(value)
573        else:
574            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
575                  % (value, self.map)
576
577    # Generate printable string version of value.
578    def __str__(self):
579        return str(self.value)
580
581class _DictEnum(object):
582    # Constructor.  Value must be a key in the type's map dictionary.
583    def __init__(self, value):
584        if value in self.map:
585            self.value = value
586            self.index = self.map[value]
587        else:
588            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
589                  % (value, self.map.keys())
590
591    # Generate printable string version of value.
592    def __str__(self):
593        return str(self.value)
594
595# Enum metaclass... calling Enum(foo) generates a new type (class)
596# that derives from _ListEnum or _DictEnum as appropriate.
597class Enum(type):
598    # counter to generate unique names for generated classes
599    counter = 1
600
601    def __new__(cls, map):
602        if isinstance(map, dict):
603            base = _DictEnum
604            keys = map.keys()
605        elif isinstance(map, list):
606            base = _ListEnum
607            keys = map
608        else:
609            raise TypeError, "Enum map must be list or dict (got %s)" % map
610        classname = "Enum%04d" % Enum.counter
611        Enum.counter += 1
612        # New class derives from selected base, and gets a 'map'
613        # attribute containing the specified list or dict.
614        return type.__new__(cls, classname, (base,), { 'map': map })
615
616
617#
618# "Constants"... handy aliases for various values.
619#
620
621# For compatibility with C++ bool constants.
622false = False
623true = True
624
625# Some memory range specifications use this as a default upper bound.
626MAX_ADDR = 2 ** 63
627
628# For power-of-two sizing, e.g. 64*K gives an integer value 65536.
629K = 1024
630M = K*K
631G = K*M
632
633#####################################################################
634#
635# Object description loading.
636#
637# The final step is to define the classes corresponding to M5 objects
638# and their parameters.  These classes are described in .odesc files
639# in the source tree.  This code walks the tree to find those files
640# and loads up the descriptions (by evaluating them in pieces as
641# Python code).
642#
643#
644# Because SimObject classes inherit from other SimObject classes, and
645# can use arbitrary other SimObject classes as parameter types, we
646# have to do this in three steps:
647#
648# 1. Walk the tree to find all the .odesc files.  Note that the base
649# of the filename *must* match the class name.  This step builds a
650# mapping from class names to file paths.
651#
652# 2. Start generating empty class definitions (via def_class()) using
653# the OBJECT field of the .odesc files to determine inheritance.
654# def_class() recurses on demand to define needed base classes before
655# derived classes.
656#
657# 3. Now that all of the classes are defined, go through the .odesc
658# files one more time loading the parameter descriptions.
659#
660#####################################################################
661
662# dictionary: maps object names to file paths
663odesc_file = {}
664
665# dictionary: maps object names to boolean flag indicating whether
666# class definition was loaded yet.  Since SimObject is defined in
667# m5.config.py, count it as loaded.
668odesc_loaded = { 'SimObject': True }
669
670# Find odesc files in namelist and initialize odesc_file and
671# odesc_loaded dictionaries.  Called via os.path.walk() (see below).
672def find_odescs(process, dirpath, namelist):
673    # Prune out SCCS directories so we don't process s.*.odesc files.
674    i = 0
675    while i < len(namelist):
676        if namelist[i] == "SCCS":
677            del namelist[i]
678        else:
679            i = i + 1
680    # Find .odesc files and record them.
681    for name in namelist:
682        if name.endswith('.odesc'):
683            objname = name[:name.rindex('.odesc')]
684            path = os.path.join(dirpath, name)
685            if odesc_file.has_key(objname):
686                print "Warning: duplicate object names:", \
687                      odesc_file[objname], path
688            odesc_file[objname] = path
689            odesc_loaded[objname] = False
690
691
692# Regular expression string for parsing .odesc files.
693file_re_string = r'''
694^OBJECT: \s* (\w+) \s* \( \s* (\w+) \s* \)
695\s*
696^PARAMS: \s*\n ( (\s+.*\n)* )
697'''
698
699# Compiled regular expression object.
700file_re = re.compile(file_re_string, re.MULTILINE | re.VERBOSE)
701
702# .odesc file parsing function.  Takes a filename and returns tuple of
703# object name, object base, and parameter description section.
704def parse_file(path):
705    f = open(path, 'r').read()
706    m = file_re.search(f)
707    if not m:
708        print "Can't parse", path
709        sys.exit(1)
710    return (m.group(1), m.group(2), m.group(3))
711
712# Define SimObject class based on description in specified filename.
713# Class itself is empty except for _name attribute; parameter
714# descriptions will be loaded later.  Will recurse to define base
715# classes as needed before defining specified class.
716def def_class(path):
717    # load & parse file
718    (obj, parent, params) = parse_file(path)
719    # check to see if base class is defined yet; define it if not
720    if not odesc_loaded.has_key(parent):
721        print "No .odesc file found for", parent
722        sys.exit(1)
723    if not odesc_loaded[parent]:
724        def_class(odesc_file[parent])
725    # define the class.  The _name attribute of the class lets us
726    # track the actual SimObject class name even when we derive new
727    # subclasses in scripts (to provide new parameter value settings).
728    s = "class %s(%s): _name = '%s'" % (obj, parent, obj)
729    try:
730        # execute in global namespace, so new class will be globally
731        # visible
732        exec s in globals()
733    except Exception, exc:
734        print "Object error in %s:" % path, exc
735    # mark this file as loaded
736    odesc_loaded[obj] = True
737
738# Munge an arbitrary Python code string to get it to execute (mostly
739# dealing with indentation).  Stolen from isa_parser.py... see
740# comments there for a more detailed description.
741def fixPythonIndentation(s):
742    # get rid of blank lines first
743    s = re.sub(r'(?m)^\s*\n', '', s);
744    if (s != '' and re.match(r'[ \t]', s[0])):
745        s = 'if 1:\n' + s
746    return s
747
748# Load parameter descriptions from .odesc file.  Object class must
749# already be defined.
750def def_params(path):
751    # load & parse file
752    (obj_name, parent_name, param_code) = parse_file(path)
753    # initialize param dict
754    param_dict = {}
755    # execute parameter descriptions.
756    try:
757        # "in globals(), param_dict" makes exec use the current
758        # globals as the global namespace (so all of the Param
759        # etc. objects are visible) and param_dict as the local
760        # namespace (so the newly defined parameter variables will be
761        # entered into param_dict).
762        exec fixPythonIndentation(param_code) in globals(), param_dict
763    except Exception, exc:
764        print "Param error in %s:" % path, exc
765        return
766    # Convert object name string to Python class object
767    obj = eval(obj_name)
768    # Set the object's parameter description dictionary (see MetaConfigNode).
769    obj.set_param_dict(param_dict)
770
771
772# Walk directory tree to find .odesc files.
773# Someday we'll have to make the root path an argument instead of
774# hard-coding it.  For now the assumption is you're running this in
775# util/config.
776root = '../..'
777os.path.walk(root, find_odescs, None)
778
779# Iterate through file dictionary and define classes.
780for objname, path in odesc_file.iteritems():
781    if not odesc_loaded[objname]:
782        def_class(path)
783
784sim_object_list = odesc_loaded.keys()
785sim_object_list.sort()
786
787# Iterate through files again and load parameters.
788for path in odesc_file.itervalues():
789    def_params(path)
790
791#####################################################################
792
793# Hook to generate C++ parameter code.
794def gen_sim_code(file):
795    for objname in sim_object_list:
796        print >> file, eval("%s._sim_code()" % objname)
797
798# The final hook to generate .ini files.  Called from configuration
799# script once config is built.
800def instantiate(*objs):
801    for obj in objs:
802        obj._instantiate()
803
804
805