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