1 # Copyright (c) 2004-2005 The Regents of The University of Michigan
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.
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.
27 # Authors: Steve Reinhardt
30 import os
, re
, sys
, types
, inspect
35 from multidict
import multidict
43 class Singleton(type):
44 def __call__(cls
, *args
, **kwargs
):
45 if hasattr(cls
, '_instance'):
48 cls
._instance
= super(Singleton
, cls
).__call
__(*args
, **kwargs
)
51 #####################################################################
53 # M5 Python Configuration Utility
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.
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:
71 # cache = BaseCache(size='64KB')
72 # cache.hit_latency = 3
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.
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).
90 #####################################################################
92 #####################################################################
94 # ConfigNode/SimObject classes
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
102 # 1. Regular Python methods and variables. These must start with an
103 # underscore to be treated normally.
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.
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.
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.
134 #####################################################################
137 # dict to look up SimObjects based on path
140 def isSimObject(value
):
141 return isinstance(value
, SimObject
)
143 def isSimObjectClass(value
):
145 return issubclass(value
, SimObject
)
147 # happens if value is not a class at all
150 def isSimObjectSequence(value
):
151 if not isinstance(value
, (list, tuple)) or len(value
) == 0:
155 if not isNullPointer(val
) and not isSimObject(val
):
160 def isSimObjectClassSequence(value
):
161 if not isinstance(value
, (list, tuple)) or len(value
) == 0:
165 if not isNullPointer(val
) and not isSimObjectClass(val
):
170 def isSimObjectOrSequence(value
):
171 return isSimObject(value
) or isSimObjectSequence(value
)
173 def isSimObjectClassOrSequence(value
):
174 return isSimObjectClass(value
) or isSimObjectClassSequence(value
)
176 def isNullPointer(value
):
177 return isinstance(value
, NullSimObject
)
179 # Apply method to object.
180 # applyMethod(obj, 'meth', <args>) is equivalent to obj.meth(<args>)
181 def applyMethod(obj
, meth
, *args
, **kwargs
):
182 return getattr(obj
, meth
)(*args
, **kwargs
)
184 # If the first argument is an (non-sequence) object, apply the named
185 # method with the given arguments. If the first argument is a
186 # sequence, apply the method to each element of the sequence (a la
188 def applyOrMap(objOrSeq
, meth
, *args
, **kwargs
):
189 if not isinstance(objOrSeq
, (list, tuple)):
190 return applyMethod(objOrSeq
, meth
, *args
, **kwargs
)
192 return [applyMethod(o
, meth
, *args
, **kwargs
) for o
in objOrSeq
]
195 # The metaclass for ConfigNode (and thus for everything that derives
196 # from ConfigNode, including SimObject). This class controls how new
197 # classes that derive from ConfigNode are instantiated, and provides
198 # inherited class behavior (just like a class controls how instances
199 # of that class are instantiated, and provides inherited instance
201 class MetaSimObject(type):
202 # Attributes that can be set only at initialization time
203 init_keywords
= { 'abstract' : types
.BooleanType
,
204 'type' : types
.StringType
}
205 # Attributes that can be set any time
206 keywords
= { 'check' : types
.FunctionType
,
207 'children' : types
.ListType
,
208 'ccObject' : types
.ObjectType
}
210 # __new__ is called before __init__, and is where the statements
211 # in the body of the class definition get loaded into the class's
212 # __dict__. We intercept this to filter out parameter assignments
213 # and only allow "private" attributes to be passed to the base
214 # __new__ (starting with underscore).
215 def __new__(mcls
, name
, bases
, dict):
216 if dict.has_key('_init_dict'):
217 # must have been called from makeSubclass() rather than
218 # via Python class declaration; bypass filtering process.
221 # Copy "private" attributes (including special methods
222 # such as __new__) to the official dict. Everything else
223 # goes in _init_dict to be filtered in __init__.
225 for key
,val
in dict.items():
226 if key
.startswith('_'):
229 cls_dict
['_init_dict'] = dict
230 return super(MetaSimObject
, mcls
).__new
__(mcls
, name
, bases
, cls_dict
)
232 # subclass initialization
233 def __init__(cls
, name
, bases
, dict):
234 # calls type.__init__()... I think that's a no-op, but leave
235 # it here just in case it's not.
236 super(MetaSimObject
, cls
).__init
__(name
, bases
, dict)
238 # initialize required attributes
239 cls
._params
= multidict()
240 cls
._values
= multidict()
241 cls
._ports
= multidict()
242 cls
._instantiated
= False # really instantiated or subclassed
243 cls
._anon
_subclass
_counter
= 0
245 # We don't support multiple inheritance. If you want to, you
246 # must fix multidict to deal with it properly.
248 raise TypeError, "SimObjects do not support multiple inheritance"
252 # the only time the following is not true is when we define
253 # the SimObject class itself
254 if isinstance(base
, MetaSimObject
):
255 cls
._params
.parent
= base
._params
256 cls
._values
.parent
= base
._values
257 cls
._ports
.parent
= base
._ports
258 base
._instantiated
= True
260 # now process the _init_dict items
261 for key
,val
in cls
._init
_dict
.items():
262 if isinstance(val
, (types
.FunctionType
, types
.TypeType
)):
263 type.__setattr
__(cls
, key
, val
)
266 elif isinstance(val
, ParamDesc
):
267 cls
._new
_param
(key
, val
)
270 elif isinstance(val
, Port
):
271 cls
._ports
[key
] = val
273 # init-time-only keywords
274 elif cls
.init_keywords
.has_key(key
):
275 cls
._set
_keyword
(key
, val
, cls
.init_keywords
[key
])
277 # default: use normal path (ends up in __setattr__)
279 setattr(cls
, key
, val
)
281 # Pull the deep-copy memoization dict out of the class dict if
283 memo
= cls
.__dict
__.get('_memo', {})
285 # Handle SimObject values
286 for key
,val
in cls
._values
.iteritems():
287 # SimObject instances need to be promoted to classes.
288 # Existing classes should not have any instance values, so
289 # these can only occur at the lowest level dict (the
290 # parameters just being set in this class definition).
291 if isSimObjectOrSequence(val
):
292 assert(val
== cls
._values
.local
[key
])
293 cls
._values
[key
] = applyOrMap(val
, 'makeClass', memo
)
294 # SimObject classes need to be subclassed so that
295 # parameters that get set at this level only affect this
296 # level and derivatives.
297 elif isSimObjectClassOrSequence(val
):
298 assert(not cls
._values
.local
.has_key(key
))
299 cls
._values
[key
] = applyOrMap(val
, 'makeSubclass', {}, memo
)
302 def _set_keyword(cls
, keyword
, val
, kwtype
):
303 if not isinstance(val
, kwtype
):
304 raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
305 (keyword
, type(val
), kwtype
)
306 if isinstance(val
, types
.FunctionType
):
307 val
= classmethod(val
)
308 type.__setattr
__(cls
, keyword
, val
)
310 def _new_param(cls
, name
, value
):
311 cls
._params
[name
] = value
312 if hasattr(value
, 'default'):
313 setattr(cls
, name
, value
.default
)
315 # Set attribute (called on foo.attr = value when foo is an
316 # instance of class cls).
317 def __setattr__(cls
, attr
, value
):
318 # normal processing for private attributes
319 if attr
.startswith('_'):
320 type.__setattr
__(cls
, attr
, value
)
323 if cls
.keywords
.has_key(attr
):
324 cls
._set
_keyword
(attr
, value
, cls
.keywords
[attr
])
327 if cls
._ports
.has_key(attr
):
328 self
._ports
[attr
].connect(self
, attr
, value
)
331 # must be SimObject param
332 param
= cls
._params
.get(attr
, None)
334 # It's ok: set attribute by delegating to 'object' class.
335 if isSimObjectOrSequence(value
) and cls
._instantiated
:
336 raise AttributeError, \
337 "Cannot set SimObject parameter '%s' after\n" \
338 " class %s has been instantiated or subclassed" \
339 % (attr
, cls
.__name
__)
341 cls
._values
[attr
] = param
.convert(value
)
343 msg
= "%s\nError setting param %s.%s to %s\n" % \
344 (e
, cls
.__name
__, attr
, value
)
347 # I would love to get rid of this
348 elif isSimObjectOrSequence(value
):
349 cls
._values
[attr
] = value
351 raise AttributeError, \
352 "Class %s has no parameter %s" % (cls
.__name
__, attr
)
354 def __getattr__(cls
, attr
):
355 if cls
._values
.has_key(attr
):
356 return cls
._values
[attr
]
358 raise AttributeError, \
359 "object '%s' has no attribute '%s'" % (cls
.__name
__, attr
)
361 # Create a subclass of this class. Basically a function interface
362 # to the standard Python class definition mechanism, primarily for
363 # internal use. 'memo' dict param supports "deep copy" (really
364 # "deep subclass") operations... within a given operation,
365 # multiple references to a class should result in a single
366 # subclass object with multiple references to it (as opposed to
367 # mutiple unique subclasses).
368 def makeSubclass(cls
, init_dict
, memo
= {}):
369 subcls
= memo
.get(cls
)
371 name
= cls
.__name
__ + '_' + str(cls
._anon
_subclass
_counter
)
372 cls
._anon
_subclass
_counter
+= 1
373 subcls
= MetaSimObject(name
, (cls
,),
374 { '_init_dict': init_dict
, '_memo': memo
})
377 # The ConfigNode class is the root of the special hierarchy. Most of
378 # the code in this class deals with the configuration hierarchy itself
379 # (parent/child node relationships).
380 class SimObject(object):
381 # Specify metaclass. Any class inheriting from SimObject will
382 # get this metaclass.
383 __metaclass__
= MetaSimObject
385 # __new__ operator allocates new instances of the class. We
386 # override it here just to support "deep instantiation" operation
387 # via the _memo dict. When recursively instantiating an object
388 # hierarchy we want to make sure that each class is instantiated
389 # only once, and that if there are multiple references to the same
390 # original class, we end up with the corresponding instantiated
391 # references all pointing to the same instance.
392 def __new__(cls
, _memo
= None, **kwargs
):
393 if _memo
is not None and _memo
.has_key(cls
):
394 # return previously instantiated object
395 assert(len(kwargs
) == 0)
398 # Need a new one... if it needs to be memoized, this will
399 # happen in __init__. We defer the insertion until then
400 # so __init__ can use the memo dict to tell whether or not
401 # to perform the initialization.
402 return super(SimObject
, cls
).__new
__(cls
, **kwargs
)
404 # Initialize new instance previously allocated by __new__. For
405 # objects with SimObject-valued params, we need to recursively
406 # instantiate the classes represented by those param values as
407 # well (in a consistent "deep copy"-style fashion; see comment
409 def __init__(self
, _memo
= None, **kwargs
):
410 if _memo
is not None:
411 # We're inside a "deep instantiation"
412 assert(isinstance(_memo
, dict))
413 assert(len(kwargs
) == 0)
414 if _memo
.has_key(self
.__class
__):
415 # __new__ returned an existing, already initialized
416 # instance, so there's nothing to do here
417 assert(_memo
[self
.__class
__] == self
)
419 # no pre-existing object, so remember this one here
420 _memo
[self
.__class
__] = self
422 # This is a new top-level instantiation... don't memoize
423 # this objcet, but prepare to memoize any recursively
424 # instantiated objects.
427 self
.__class
__._instantiated
= True
430 # Inherit parameter values from class using multidict so
431 # individual value settings can be overridden.
432 self
._values
= multidict(self
.__class
__._values
)
433 # For SimObject-valued parameters, the class should have
434 # classes (not instances) for the values. We need to
435 # instantiate these classes rather than just inheriting the
437 for key
,val
in self
.__class
__._values
.iteritems():
438 if isSimObjectClass(val
):
439 setattr(self
, key
, val(_memo
))
440 elif isSimObjectClassSequence(val
) and len(val
):
441 setattr(self
, key
, [ v(_memo
) for v
in val
])
442 # apply attribute assignments from keyword args, if any
443 for key
,val
in kwargs
.iteritems():
444 setattr(self
, key
, val
)
446 self
._ccObject
= None # pointer to C++ object
447 self
._port
_map
= {} # map of port connections
449 # Use this instance as a template to create a new class.
450 def makeClass(self
, memo
= {}):
453 cls
= self
.__class
__.makeSubclass(self
._values
.local
)
457 # Direct instantiation of instances (cloning) is no longer
458 # allowed; must generate class from instance first.
459 def __call__(self
, **kwargs
):
460 raise TypeError, "cannot instantiate SimObject; "\
461 "use makeClass() to make class first"
463 def __getattr__(self
, attr
):
464 if self
._ports
.has_key(attr
):
465 # return reference that can be assigned to another port
467 return self
._ports
[attr
].makeRef(self
, attr
)
469 if self
._values
.has_key(attr
):
470 return self
._values
[attr
]
472 raise AttributeError, "object '%s' has no attribute '%s'" \
473 % (self
.__class
__.__name
__, attr
)
475 # Set attribute (called on foo.attr = value when foo is an
476 # instance of class cls).
477 def __setattr__(self
, attr
, value
):
478 # normal processing for private attributes
479 if attr
.startswith('_'):
480 object.__setattr
__(self
, attr
, value
)
483 if self
._ports
.has_key(attr
):
484 # set up port connection
485 self
._ports
[attr
].connect(self
, attr
, value
)
488 # must be SimObject param
489 param
= self
._params
.get(attr
, None)
491 # It's ok: set attribute by delegating to 'object' class.
493 value
= param
.convert(value
)
495 msg
= "%s\nError setting param %s.%s to %s\n" % \
496 (e
, self
.__class
__.__name
__, attr
, value
)
499 # I would love to get rid of this
500 elif isSimObjectOrSequence(value
):
503 raise AttributeError, "Class %s has no parameter %s" \
504 % (self
.__class
__.__name
__, attr
)
506 # clear out old child with this name, if any
507 self
.clear_child(attr
)
509 if isSimObject(value
):
510 value
.set_path(self
, attr
)
511 elif isSimObjectSequence(value
):
512 value
= SimObjVector(value
)
513 [v
.set_path(self
, "%s%d" % (attr
, i
)) for i
,v
in enumerate(value
)]
515 self
._values
[attr
] = value
517 # this hack allows tacking a '[0]' onto parameters that may or may
518 # not be vectors, and always getting the first element (e.g. cpus)
519 def __getitem__(self
, key
):
522 raise TypeError, "Non-zero index '%s' to SimObject" % key
524 # clear out children with given name, even if it's a vector
525 def clear_child(self
, name
):
526 if not self
._children
.has_key(name
):
528 child
= self
._children
[name
]
529 if isinstance(child
, SimObjVector
):
530 for i
in xrange(len(child
)):
531 del self
._children
["s%d" % (name
, i
)]
532 del self
._children
[name
]
534 def add_child(self
, name
, value
):
535 self
._children
[name
] = value
537 def set_path(self
, parent
, name
):
538 if not hasattr(self
, '_parent'):
539 self
._parent
= parent
541 parent
.add_child(name
, self
)
544 if not hasattr(self
, '_parent'):
546 ppath
= self
._parent
.path()
549 return ppath
+ "." + self
._name
557 def find_any(self
, ptype
):
558 if isinstance(self
, ptype
):
562 for child
in self
._children
.itervalues():
563 if isinstance(child
, ptype
):
564 if found_obj
!= None and child
!= found_obj
:
565 raise AttributeError, \
566 'parent.any matched more than one: %s %s' % \
567 (found_obj
.path
, child
.path
)
570 for pname
,pdesc
in self
._params
.iteritems():
571 if issubclass(pdesc
.ptype
, ptype
):
572 match_obj
= self
._values
[pname
]
573 if found_obj
!= None and found_obj
!= match_obj
:
574 raise AttributeError, \
575 'parent.any matched more than one: %s' % obj
.path
576 found_obj
= match_obj
577 return found_obj
, found_obj
!= None
579 def unproxy(self
, base
):
583 print '[' + self
.path() + ']' # .ini section header
585 instanceDict
[self
.path()] = self
587 if hasattr(self
, 'type') and not isinstance(self
, ParamContext
):
588 print 'type=%s' % self
.type
590 child_names
= self
._children
.keys()
592 np_child_names
= [c
for c
in child_names \
593 if not isinstance(self
._children
[c
], ParamContext
)]
594 if len(np_child_names
):
595 print 'children=%s' % ' '.join(np_child_names
)
597 param_names
= self
._params
.keys()
599 for param
in param_names
:
600 value
= self
._values
.get(param
, None)
604 value
= value
.unproxy(self
)
606 print >> sys
.stderr
, \
607 "Error in unproxying param '%s' of %s" % \
610 setattr(self
, param
, value
)
611 print '%s=%s' % (param
, self
._values
[param
].ini_str())
613 print # blank line between objects
615 for child
in child_names
:
616 self
._children
[child
].print_ini()
618 # Call C++ to create C++ object corresponding to this object and
619 # (recursively) all its children
620 def createCCObject(self
):
624 self
._ccObject
= m5
.main
.createSimObject(self
.path())
625 for child
in self
._children
.itervalues():
626 child
.createCCObject()
628 # Create C++ port connections corresponding to the connections in
629 # _port_map (& recursively for all children)
630 def connectPorts(self
):
631 for portRef
in self
._port
_map
.itervalues():
632 applyOrMap(portRef
, 'ccConnect')
633 for child
in self
._children
.itervalues():
636 # generate output file for 'dot' to display as a pretty graph.
637 # this code is currently broken.
638 def outputDot(self
, dot
):
639 label
= "{%s|" % self
.path
640 if isSimObject(self
.realtype
):
641 label
+= '%s|' % self
.type
644 # instantiate children in same order they were added for
645 # backward compatibility (else we can end up with cpu1
647 for c
in self
.children
:
648 dot
.add_edge(pydot
.Edge(self
.path
,c
.path
, style
="bold"))
651 for param
in self
.params
:
653 if param
.value
is None:
654 raise AttributeError, 'Parameter with no value'
657 string
= param
.string(value
)
659 msg
= 'exception in %s:%s\n%s' % (self
.name
, param
.name
, e
)
663 if isSimObject(param
.ptype
) and string
!= "Null":
664 simobjs
.append(string
)
666 label
+= '%s = %s\\n' % (param
.name
, string
)
669 label
+= "|<%s> %s" % (so
, so
)
670 dot
.add_edge(pydot
.Edge("%s:%s" % (self
.path
, so
), so
,
673 dot
.add_node(pydot
.Node(self
.path
,shape
="Mrecord",label
=label
))
675 # recursively dump out children
676 for c
in self
.children
:
679 class ParamContext(SimObject
):
682 #####################################################################
684 # Proxy object support.
686 #####################################################################
688 class BaseProxy(object):
689 def __init__(self
, search_self
, search_up
):
690 self
._search
_self
= search_self
691 self
._search
_up
= search_up
692 self
._multiplier
= None
694 def __setattr__(self
, attr
, value
):
695 if not attr
.startswith('_'):
696 raise AttributeError, 'cannot set attribute on proxy object'
697 super(BaseProxy
, self
).__setattr
__(attr
, value
)
699 # support multiplying proxies by constants
700 def __mul__(self
, other
):
701 if not isinstance(other
, (int, long, float)):
702 raise TypeError, "Proxy multiplier must be integer"
703 if self
._multiplier
== None:
704 self
._multiplier
= other
706 # support chained multipliers
707 self
._multiplier
*= other
712 def _mulcheck(self
, result
):
713 if self
._multiplier
== None:
715 return result
* self
._multiplier
717 def unproxy(self
, base
):
721 if self
._search
_self
:
722 result
, done
= self
.find(obj
)
726 try: obj
= obj
._parent
729 result
, done
= self
.find(obj
)
732 raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \
733 (self
.path(), base
.path())
735 if isinstance(result
, BaseProxy
):
737 raise RuntimeError, "Cycle in unproxy"
738 result
= result
.unproxy(obj
)
740 return self
._mulcheck
(result
)
742 def getindex(obj
, index
):
750 # if index is 0 and item is not subscriptable, just
751 # use item itself (so cpu[0] works on uniprocessors)
753 getindex
= staticmethod(getindex
)
755 def set_param_desc(self
, pdesc
):
758 class AttrProxy(BaseProxy
):
759 def __init__(self
, search_self
, search_up
, attr
):
760 super(AttrProxy
, self
).__init
__(search_self
, search_up
)
764 def __getattr__(self
, attr
):
765 # python uses __bases__ internally for inheritance
766 if attr
.startswith('_'):
767 return super(AttrProxy
, self
).__getattr
__(self
, attr
)
768 if hasattr(self
, '_pdesc'):
769 raise AttributeError, "Attribute reference on bound proxy"
770 self
._modifiers
.append(attr
)
773 # support indexing on proxies (e.g., Self.cpu[0])
774 def __getitem__(self
, key
):
775 if not isinstance(key
, int):
776 raise TypeError, "Proxy object requires integer index"
777 self
._modifiers
.append(key
)
782 val
= getattr(obj
, self
._attr
)
786 val
= val
.unproxy(obj
)
787 for m
in self
._modifiers
:
788 if isinstance(m
, str):
789 val
= getattr(val
, m
)
790 elif isinstance(m
, int):
793 assert("Item must be string or integer")
795 val
= val
.unproxy(obj
)
800 for m
in self
._modifiers
:
801 if isinstance(m
, str):
803 elif isinstance(m
, int):
806 assert("Item must be string or integer")
809 class AnyProxy(BaseProxy
):
811 return obj
.find_any(self
._pdesc
.ptype
)
817 if isinstance(obj
, (BaseProxy
, EthernetAddr
)):
819 elif isinstance(obj
, (list, tuple)):
825 class ProxyFactory(object):
826 def __init__(self
, search_self
, search_up
):
827 self
.search_self
= search_self
828 self
.search_up
= search_up
830 def __getattr__(self
, attr
):
832 return AnyProxy(self
.search_self
, self
.search_up
)
834 return AttrProxy(self
.search_self
, self
.search_up
, attr
)
836 # global objects for handling proxies
837 Parent
= ProxyFactory(search_self
= False, search_up
= True)
838 Self
= ProxyFactory(search_self
= True, search_up
= False)
840 #####################################################################
842 # Parameter description classes
844 # The _params dictionary in each class maps parameter names to
845 # either a Param or a VectorParam object. These objects contain the
846 # parameter description string, the parameter type, and the default
847 # value (loaded from the PARAM section of the .odesc files). The
848 # _convert() method on these objects is used to force whatever value
849 # is assigned to the parameter to the appropriate type.
851 # Note that the default values are loaded into the class's attribute
852 # space when the parameter dictionary is initialized (in
853 # MetaConfigNode._setparams()); after that point they aren't used.
855 #####################################################################
857 # Dummy base class to identify types that are legitimate for SimObject
859 class ParamValue(object):
861 # default for printing to .ini file is regular string conversion.
862 # will be overridden in some cases
866 # allows us to blithely call unproxy() on things without checking
867 # if they're really proxies or not
868 def unproxy(self
, base
):
871 # Regular parameter description.
872 class ParamDesc(object):
873 def __init__(self
, ptype_str
, ptype
, *args
, **kwargs
):
874 self
.ptype_str
= ptype_str
875 # remember ptype only if it is provided
883 self
.default
= args
[0]
886 raise TypeError, 'too many arguments'
888 if kwargs
.has_key('desc'):
889 assert(not hasattr(self
, 'desc'))
890 self
.desc
= kwargs
['desc']
893 if kwargs
.has_key('default'):
894 assert(not hasattr(self
, 'default'))
895 self
.default
= kwargs
['default']
896 del kwargs
['default']
899 raise TypeError, 'extra unknown kwargs %s' % kwargs
901 if not hasattr(self
, 'desc'):
902 raise TypeError, 'desc attribute missing'
904 def __getattr__(self
, attr
):
907 ptype
= eval(self
.ptype_str
, m5
.objects
.__dict
__)
908 if not isinstance(ptype
, type):
909 panic("Param qualifier is not a type: %s" % self
.ptype
)
914 raise AttributeError, "'%s' object has no attribute '%s'" % \
915 (type(self
).__name
__, attr
)
917 def convert(self
, value
):
918 if isinstance(value
, BaseProxy
):
919 value
.set_param_desc(self
)
921 if not hasattr(self
, 'ptype') and isNullPointer(value
):
922 # deferred evaluation of SimObject; continue to defer if
923 # we're just assigning a null pointer
925 if isinstance(value
, self
.ptype
):
927 if isNullPointer(value
) and issubclass(self
.ptype
, SimObject
):
929 return self
.ptype(value
)
931 # Vector-valued parameter description. Just like ParamDesc, except
932 # that the value is a vector (list) of the specified type instead of a
935 class VectorParamValue(list):
937 return ' '.join([v
.ini_str() for v
in self
])
939 def unproxy(self
, base
):
940 return [v
.unproxy(base
) for v
in self
]
942 class SimObjVector(VectorParamValue
):
947 class VectorParamDesc(ParamDesc
):
948 # Convert assigned value to appropriate type. If the RHS is not a
949 # list or tuple, it generates a single-element list.
950 def convert(self
, value
):
951 if isinstance(value
, (list, tuple)):
952 # list: coerce each element into new list
953 tmp_list
= [ ParamDesc
.convert(self
, v
) for v
in value
]
954 if isSimObjectSequence(tmp_list
):
955 return SimObjVector(tmp_list
)
957 return VectorParamValue(tmp_list
)
959 # singleton: leave it be (could coerce to a single-element
960 # list here, but for some historical reason we don't...
961 return ParamDesc
.convert(self
, value
)
964 class ParamFactory(object):
965 def __init__(self
, param_desc_class
, ptype_str
= None):
966 self
.param_desc_class
= param_desc_class
967 self
.ptype_str
= ptype_str
969 def __getattr__(self
, attr
):
971 attr
= self
.ptype_str
+ '.' + attr
972 return ParamFactory(self
.param_desc_class
, attr
)
974 # E.g., Param.Int(5, "number of widgets")
975 def __call__(self
, *args
, **kwargs
):
976 caller_frame
= inspect
.currentframe().f_back
979 ptype
= eval(self
.ptype_str
,
980 caller_frame
.f_globals
, caller_frame
.f_locals
)
981 if not isinstance(ptype
, type):
983 "Param qualifier is not a type: %s" % ptype
985 # if name isn't defined yet, assume it's a SimObject, and
986 # try to resolve it later
988 return self
.param_desc_class(self
.ptype_str
, ptype
, *args
, **kwargs
)
990 Param
= ParamFactory(ParamDesc
)
991 VectorParam
= ParamFactory(VectorParamDesc
)
993 #####################################################################
997 # Though native Python types could be used to specify parameter types
998 # (the 'ptype' field of the Param and VectorParam classes), it's more
999 # flexible to define our own set of types. This gives us more control
1000 # over how Python expressions are converted to values (via the
1001 # __init__() constructor) and how these values are printed out (via
1002 # the __str__() conversion method). Eventually we'll need these types
1003 # to correspond to distinct C++ types as well.
1005 #####################################################################
1007 # superclass for "numeric" parameter values, to emulate math
1008 # operations in a type-safe way. e.g., a Latency times an int returns
1009 # a new Latency object.
1010 class NumericParamValue(ParamValue
):
1012 return str(self
.value
)
1014 def __float__(self
):
1015 return float(self
.value
)
1017 # hook for bounds checking
1021 def __mul__(self
, other
):
1022 newobj
= self
.__class
__(self
)
1023 newobj
.value
*= other
1029 def __div__(self
, other
):
1030 newobj
= self
.__class
__(self
)
1031 newobj
.value
/= other
1035 def __sub__(self
, other
):
1036 newobj
= self
.__class
__(self
)
1037 newobj
.value
-= other
1041 class Range(ParamValue
):
1042 type = int # default; can be overridden in subclasses
1043 def __init__(self
, *args
, **kwargs
):
1045 def handle_kwargs(self
, kwargs
):
1047 self
.second
= self
.type(kwargs
.pop('end'))
1048 elif 'size' in kwargs
:
1049 self
.second
= self
.first
+ self
.type(kwargs
.pop('size')) - 1
1051 raise TypeError, "Either end or size must be specified"
1054 self
.first
= self
.type(kwargs
.pop('start'))
1055 handle_kwargs(self
, kwargs
)
1057 elif len(args
) == 1:
1059 self
.first
= self
.type(args
[0])
1060 handle_kwargs(self
, kwargs
)
1061 elif isinstance(args
[0], Range
):
1062 self
.first
= self
.type(args
[0].first
)
1063 self
.second
= self
.type(args
[0].second
)
1065 self
.first
= self
.type(0)
1066 self
.second
= self
.type(args
[0]) - 1
1068 elif len(args
) == 2:
1069 self
.first
= self
.type(args
[0])
1070 self
.second
= self
.type(args
[1])
1072 raise TypeError, "Too many arguments specified"
1075 raise TypeError, "too many keywords: %s" % kwargs
.keys()
1078 return '%s:%s' % (self
.first
, self
.second
)
1080 # Metaclass for bounds-checked integer parameters. See CheckedInt.
1081 class CheckedIntType(type):
1082 def __init__(cls
, name
, bases
, dict):
1083 super(CheckedIntType
, cls
).__init
__(name
, bases
, dict)
1085 # CheckedInt is an abstract base class, so we actually don't
1086 # want to do any processing on it... the rest of this code is
1087 # just for classes that derive from CheckedInt.
1088 if name
== 'CheckedInt':
1091 if not (hasattr(cls
, 'min') and hasattr(cls
, 'max')):
1092 if not (hasattr(cls
, 'size') and hasattr(cls
, 'unsigned')):
1093 panic("CheckedInt subclass %s must define either\n" \
1094 " 'min' and 'max' or 'size' and 'unsigned'\n" \
1098 cls
.max = 2 ** cls
.size
- 1
1100 cls
.min = -(2 ** (cls
.size
- 1))
1101 cls
.max = (2 ** (cls
.size
- 1)) - 1
1103 # Abstract superclass for bounds-checked integer parameters. This
1104 # class is subclassed to generate parameter classes with specific
1105 # bounds. Initialization of the min and max bounds is done in the
1106 # metaclass CheckedIntType.__init__.
1107 class CheckedInt(NumericParamValue
):
1108 __metaclass__
= CheckedIntType
1111 if not self
.min <= self
.value
<= self
.max:
1112 raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
1113 (self
.min, self
.value
, self
.max)
1115 def __init__(self
, value
):
1116 if isinstance(value
, str):
1117 self
.value
= toInteger(value
)
1118 elif isinstance(value
, (int, long, float)):
1119 self
.value
= long(value
)
1122 class Int(CheckedInt
): size
= 32; unsigned
= False
1123 class Unsigned(CheckedInt
): size
= 32; unsigned
= True
1125 class Int8(CheckedInt
): size
= 8; unsigned
= False
1126 class UInt8(CheckedInt
): size
= 8; unsigned
= True
1127 class Int16(CheckedInt
): size
= 16; unsigned
= False
1128 class UInt16(CheckedInt
): size
= 16; unsigned
= True
1129 class Int32(CheckedInt
): size
= 32; unsigned
= False
1130 class UInt32(CheckedInt
): size
= 32; unsigned
= True
1131 class Int64(CheckedInt
): size
= 64; unsigned
= False
1132 class UInt64(CheckedInt
): size
= 64; unsigned
= True
1134 class Counter(CheckedInt
): size
= 64; unsigned
= True
1135 class Tick(CheckedInt
): size
= 64; unsigned
= True
1136 class TcpPort(CheckedInt
): size
= 16; unsigned
= True
1137 class UdpPort(CheckedInt
): size
= 16; unsigned
= True
1139 class Percent(CheckedInt
): min = 0; max = 100
1141 class Float(ParamValue
, float):
1144 class MemorySize(CheckedInt
):
1147 def __init__(self
, value
):
1148 if isinstance(value
, MemorySize
):
1149 self
.value
= value
.value
1151 self
.value
= toMemorySize(value
)
1154 class MemorySize32(CheckedInt
):
1157 def __init__(self
, value
):
1158 if isinstance(value
, MemorySize
):
1159 self
.value
= value
.value
1161 self
.value
= toMemorySize(value
)
1164 class Addr(CheckedInt
):
1167 def __init__(self
, value
):
1168 if isinstance(value
, Addr
):
1169 self
.value
= value
.value
1172 self
.value
= toMemorySize(value
)
1174 self
.value
= long(value
)
1177 class AddrRange(Range
):
1180 # String-valued parameter. Just mixin the ParamValue class
1181 # with the built-in str class.
1182 class String(ParamValue
,str):
1185 # Boolean parameter type. Python doesn't let you subclass bool, since
1186 # it doesn't want to let you create multiple instances of True and
1187 # False. Thus this is a little more complicated than String.
1188 class Bool(ParamValue
):
1189 def __init__(self
, value
):
1191 self
.value
= toBool(value
)
1193 self
.value
= bool(value
)
1196 return str(self
.value
)
1203 def IncEthernetAddr(addr
, val
= 1):
1204 bytes
= map(lambda x
: int(x
, 16), addr
.split(':'))
1206 for i
in (5, 4, 3, 2, 1):
1207 val
,rem
= divmod(bytes
[i
], 256)
1212 assert(bytes
[0] <= 255)
1213 return ':'.join(map(lambda x
: '%02x' % x
, bytes
))
1215 class NextEthernetAddr(object):
1216 addr
= "00:90:00:00:00:01"
1218 def __init__(self
, inc
= 1):
1219 self
.value
= NextEthernetAddr
.addr
1220 NextEthernetAddr
.addr
= IncEthernetAddr(NextEthernetAddr
.addr
, inc
)
1222 class EthernetAddr(ParamValue
):
1223 def __init__(self
, value
):
1224 if value
== NextEthernetAddr
:
1228 if not isinstance(value
, str):
1229 raise TypeError, "expected an ethernet address and didn't get one"
1231 bytes
= value
.split(':')
1233 raise TypeError, 'invalid ethernet address %s' % value
1236 if not 0 <= int(byte
) <= 256:
1237 raise TypeError, 'invalid ethernet address %s' % value
1241 def unproxy(self
, base
):
1242 if self
.value
== NextEthernetAddr
:
1243 self
.addr
= self
.value().value
1247 if self
.value
== NextEthernetAddr
:
1248 if hasattr(self
, 'addr'):
1251 return "NextEthernetAddr (unresolved)"
1255 # Special class for NULL pointers. Note the special check in
1256 # make_param_value() above that lets these be assigned where a
1257 # SimObject is required.
1258 # only one copy of a particular node
1259 class NullSimObject(object):
1260 __metaclass__
= Singleton
1265 def _instantiate(self
, parent
= None, path
= ''):
1271 def unproxy(self
, base
):
1274 def set_path(self
, parent
, name
):
1279 # The only instance you'll ever need...
1280 Null
= NULL
= NullSimObject()
1282 # Enumerated types are a little more complex. The user specifies the
1283 # type as Enum(foo) where foo is either a list or dictionary of
1284 # alternatives (typically strings, but not necessarily so). (In the
1285 # long run, the integer value of the parameter will be the list index
1286 # or the corresponding dictionary value. For now, since we only check
1287 # that the alternative is valid and then spit it into a .ini file,
1288 # there's not much point in using the dictionary.)
1290 # What Enum() must do is generate a new type encapsulating the
1291 # provided list/dictionary so that specific values of the parameter
1292 # can be instances of that type. We define two hidden internal
1293 # classes (_ListEnum and _DictEnum) to serve as base classes, then
1294 # derive the new type from the appropriate base class on the fly.
1297 # Metaclass for Enum types
1298 class MetaEnum(type):
1299 def __init__(cls
, name
, bases
, init_dict
):
1300 if init_dict
.has_key('map'):
1301 if not isinstance(cls
.map, dict):
1302 raise TypeError, "Enum-derived class attribute 'map' " \
1303 "must be of type dict"
1304 # build list of value strings from map
1305 cls
.vals
= cls
.map.keys()
1307 elif init_dict
.has_key('vals'):
1308 if not isinstance(cls
.vals
, list):
1309 raise TypeError, "Enum-derived class attribute 'vals' " \
1310 "must be of type list"
1311 # build string->value map from vals sequence
1313 for idx
,val
in enumerate(cls
.vals
):
1316 raise TypeError, "Enum-derived class must define "\
1317 "attribute 'map' or 'vals'"
1319 super(MetaEnum
, cls
).__init
__(name
, bases
, init_dict
)
1321 def cpp_declare(cls
):
1322 s
= 'enum %s {\n ' % cls
.__name
__
1323 s
+= ',\n '.join(['%s = %d' % (v
,cls
.map[v
]) for v
in cls
.vals
])
1327 # Base class for enum types.
1328 class Enum(ParamValue
):
1329 __metaclass__
= MetaEnum
1332 def __init__(self
, value
):
1333 if value
not in self
.map:
1334 raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1335 % (value
, self
.vals
)
1341 ticks_per_sec
= None
1343 # how big does a rounding error need to be before we warn about it?
1344 frequency_tolerance
= 0.001 # 0.1%
1346 # convert a floting-point # of ticks to integer, and warn if rounding
1347 # discards too much precision
1348 def tick_check(float_ticks
):
1349 if float_ticks
== 0:
1351 int_ticks
= int(round(float_ticks
))
1352 err
= (float_ticks
- int_ticks
) / float_ticks
1353 if err
> frequency_tolerance
:
1354 print >> sys
.stderr
, "Warning: rounding error > tolerance"
1355 print >> sys
.stderr
, " %f rounded to %d" % (float_ticks
, int_ticks
)
1359 def getLatency(value
):
1360 if isinstance(value
, Latency
) or isinstance(value
, Clock
):
1362 elif isinstance(value
, Frequency
) or isinstance(value
, RootClock
):
1363 return 1 / value
.value
1364 elif isinstance(value
, str):
1366 return toLatency(value
)
1369 return 1 / toFrequency(value
)
1372 raise ValueError, "Invalid Frequency/Latency value '%s'" % value
1375 class Latency(NumericParamValue
):
1376 def __init__(self
, value
):
1377 self
.value
= getLatency(value
)
1379 def __getattr__(self
, attr
):
1380 if attr
in ('latency', 'period'):
1382 if attr
== 'frequency':
1383 return Frequency(self
)
1384 raise AttributeError, "Latency object has no attribute '%s'" % attr
1386 # convert latency to ticks
1388 return str(tick_check(self
.value
* ticks_per_sec
))
1390 class Frequency(NumericParamValue
):
1391 def __init__(self
, value
):
1392 self
.value
= 1 / getLatency(value
)
1394 def __getattr__(self
, attr
):
1395 if attr
== 'frequency':
1397 if attr
in ('latency', 'period'):
1398 return Latency(self
)
1399 raise AttributeError, "Frequency object has no attribute '%s'" % attr
1401 # convert frequency to ticks per period
1403 return self
.period
.ini_str()
1405 # Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz).
1406 # We can't inherit from Frequency because we don't want it to be directly
1407 # assignable to a regular Frequency parameter.
1408 class RootClock(ParamValue
):
1409 def __init__(self
, value
):
1410 self
.value
= 1 / getLatency(value
)
1412 def __getattr__(self
, attr
):
1413 if attr
== 'frequency':
1414 return Frequency(self
)
1415 if attr
in ('latency', 'period'):
1416 return Latency(self
)
1417 raise AttributeError, "Frequency object has no attribute '%s'" % attr
1420 return str(tick_check(self
.value
))
1422 # A generic frequency and/or Latency value. Value is stored as a latency,
1423 # but to avoid ambiguity this object does not support numeric ops (* or /).
1424 # An explicit conversion to a Latency or Frequency must be made first.
1425 class Clock(ParamValue
):
1426 def __init__(self
, value
):
1427 self
.value
= getLatency(value
)
1429 def __getattr__(self
, attr
):
1430 if attr
== 'frequency':
1431 return Frequency(self
)
1432 if attr
in ('latency', 'period'):
1433 return Latency(self
)
1434 raise AttributeError, "Frequency object has no attribute '%s'" % attr
1437 return self
.period
.ini_str()
1439 class NetworkBandwidth(float,ParamValue
):
1440 def __new__(cls
, value
):
1441 val
= toNetworkBandwidth(value
) / 8.0
1442 return super(cls
, NetworkBandwidth
).__new
__(cls
, val
)
1445 return str(self
.val
)
1448 return '%f' % (ticks_per_sec
/ float(self
))
1450 class MemoryBandwidth(float,ParamValue
):
1451 def __new__(self
, value
):
1452 val
= toMemoryBandwidth(value
)
1453 return super(cls
, MemoryBandwidth
).__new
__(cls
, val
)
1456 return str(self
.val
)
1459 return '%f' % (ticks_per_sec
/ float(self
))
1462 # "Constants"... handy aliases for various values.
1465 # Some memory range specifications use this as a default upper bound.
1468 AllMemory
= AddrRange(0, MaxAddr
)
1471 #####################################################################
1475 # Ports are used to interconnect objects in the memory system.
1477 #####################################################################
1479 # Port reference: encapsulates a reference to a particular port on a
1480 # particular SimObject.
1481 class PortRef(object):
1482 def __init__(self
, simobj
, name
, isVec
):
1483 self
.simobj
= simobj
1486 self
.isVec
= isVec
# is this a vector port?
1487 self
.peer
= None # not associated with another port yet
1488 self
.ccConnected
= False # C++ port connection done?
1490 # Set peer port reference. Called via __setattr__ as a result of
1491 # a port assignment, e.g., "obj1.port1 = obj2.port2".
1492 def setPeer(self
, other
):
1494 curMap
= self
.simobj
._port
_map
.get(self
.name
, [])
1495 self
.index
= len(curMap
)
1496 curMap
.append(other
)
1498 curMap
= self
.simobj
._port
_map
.get(self
.name
)
1499 if curMap
and not self
.isVec
:
1500 print "warning: overwriting port", self
.simobj
, self
.name
1502 self
.simobj
._port
_map
[self
.name
] = curMap
1505 # Call C++ to create corresponding port connection between C++ objects
1506 def ccConnect(self
):
1507 if self
.ccConnected
: # already done this
1510 m5
.main
.connectPorts(self
.simobj
._ccObject
, self
.name
, self
.index
,
1511 peer
.simobj
._ccObject
, peer
.name
, peer
.index
)
1512 self
.ccConnected
= True
1513 peer
.ccConnected
= True
1515 # Port description object. Like a ParamDesc object, this represents a
1516 # logical port in the SimObject class, not a particular port on a
1517 # SimObject instance. The latter are represented by PortRef objects.
1519 def __init__(self
, desc
):
1523 # Generate a PortRef for this port on the given SimObject with the
1525 def makeRef(self
, simobj
, name
):
1526 return PortRef(simobj
, name
, self
.isVec
)
1528 # Connect an instance of this port (on the given SimObject with
1529 # the given name) with the port described by the supplied PortRef
1530 def connect(self
, simobj
, name
, ref
):
1531 myRef
= self
.makeRef(simobj
, name
)
1535 # VectorPort description object. Like Port, but represents a vector
1536 # of connections (e.g., as on a Bus).
1537 class VectorPort(Port
):
1538 def __init__(self
, desc
):
1539 Port
.__init
__(self
, desc
)
1542 #####################################################################
1544 # __all__ defines the list of symbols that get exported when
1545 # 'from config import *' is invoked. Try to keep this reasonably
1546 # short to avoid polluting other namespaces.
1547 __all__
= ['SimObject', 'ParamContext', 'Param', 'VectorParam',
1549 'Enum', 'Bool', 'String', 'Float',
1550 'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1551 'Int32', 'UInt32', 'Int64', 'UInt64',
1552 'Counter', 'Addr', 'Tick', 'Percent',
1553 'TcpPort', 'UdpPort', 'EthernetAddr',
1554 'MemorySize', 'MemorySize32',
1555 'Latency', 'Frequency', 'RootClock', 'Clock',
1556 'NetworkBandwidth', 'MemoryBandwidth',
1557 'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory',
1560 'Port', 'VectorPort']