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