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