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