1 # Copyright (c) 2004-2005 The Regents of The University of Michigan
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met: redistributions of source code must retain the above copyright
7 # notice, this list of conditions and the following disclaimer;
8 # redistributions in binary form must reproduce the above copyright
9 # notice, this list of conditions and the following disclaimer in the
10 # documentation and/or other materials provided with the distribution;
11 # neither the name of the copyright holders nor the names of its
12 # contributors may be used to endorse or promote products derived from
13 # this software without specific prior written permission.
15 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 # Authors: Steve Reinhardt
30 from __future__
import generators
31 import os
, re
, sys
, types
, inspect
36 from multidict
import multidict
44 class Singleton(type):
45 def __call__(cls
, *args
, **kwargs
):
46 if hasattr(cls
, '_instance'):
49 cls
._instance
= super(Singleton
, cls
).__call
__(*args
, **kwargs
)
52 #####################################################################
54 # M5 Python Configuration Utility
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.
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:
72 # cache = BaseCache(size='64KB')
73 # cache.hit_latency = 3
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.
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).
91 #####################################################################
93 #####################################################################
95 # ConfigNode/SimObject classes
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
103 # 1. Regular Python methods and variables. These must start with an
104 # underscore to be treated normally.
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.
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.
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.
135 #####################################################################
137 def isSimObject(value
):
138 return isinstance(value
, SimObject
)
140 def isSimObjSequence(value
):
141 if not isinstance(value
, (list, tuple)):
145 if not isNullPointer(val
) and not isSimObject(val
):
150 def isNullPointer(value
):
151 return isinstance(value
, NullSimObject
)
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
159 class 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
}
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__.
177 for key
,val
in dict.items():
178 if key
.startswith('_'):
181 cls_dict
['_init_dict'] = dict
182 return super(MetaSimObject
, mcls
).__new
__(mcls
, name
, bases
, cls_dict
)
185 def __init__(cls
, name
, bases
, dict):
186 super(MetaSimObject
, cls
).__init
__(name
, bases
, dict)
188 # initialize required attributes
189 cls
._params
= multidict()
190 cls
._values
= multidict()
191 cls
._anon
_subclass
_counter
= 0
193 # We don't support multiple inheritance. If you want to, you
194 # must fix multidict to deal with it properly.
196 raise TypeError, "SimObjects do not support multiple inheritance"
200 if isinstance(base
, MetaSimObject
):
201 cls
._params
.parent
= base
._params
202 cls
._values
.parent
= base
._values
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():
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
):
216 cls
._values
[key
] = val()
217 elif isSimObjSequence(val
) and len(val
):
218 cls
._values
[key
] = [ v() for v
in val
]
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
)
226 elif isinstance(val
, ParamDesc
):
227 cls
._new
_param
(key
, val
)
229 # init-time-only keywords
230 elif cls
.init_keywords
.has_key(key
):
231 cls
._set
_keyword
(key
, val
, cls
.init_keywords
[key
])
233 # default: use normal path (ends up in __setattr__)
235 setattr(cls
, key
, val
)
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
)
245 def _new_param(cls
, name
, value
):
246 cls
._params
[name
] = value
247 if hasattr(value
, 'default'):
248 setattr(cls
, name
, value
.default
)
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
)
258 if cls
.keywords
.has_key(attr
):
259 cls
._set
_keyword
(attr
, value
, cls
.keywords
[attr
])
262 # must be SimObject param
263 param
= cls
._params
.get(attr
, None)
265 # It's ok: set attribute by delegating to 'object' class.
267 cls
._values
[attr
] = param
.convert(value
)
269 msg
= "%s\nError setting param %s.%s to %s\n" % \
270 (e
, cls
.__name
__, attr
, value
)
273 # I would love to get rid of this
274 elif isSimObject(value
) or isSimObjSequence(value
):
275 cls
._values
[attr
] = value
277 raise AttributeError, \
278 "Class %s has no parameter %s" % (cls
.__name
__, attr
)
280 def __getattr__(cls
, attr
):
281 if cls
._values
.has_key(attr
):
282 return cls
._values
[attr
]
284 raise AttributeError, \
285 "object '%s' has no attribute '%s'" % (cls
.__name
__, attr
)
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).
290 class SimObject(object):
291 # Specify metaclass. Any class inheriting from SimObject will
292 # get this metaclass.
293 __metaclass__
= MetaSimObject
295 def __init__(self
, _value_parent
= None, **kwargs
):
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
__
304 self
._values
= multidict(_value_parent
._values
)
305 for key
,val
in _value_parent
._values
.iteritems():
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
)
314 def __call__(self
, **kwargs
):
315 return self
.__class
__(_value_parent
= self
, **kwargs
)
317 def __getattr__(self
, attr
):
318 if self
._values
.has_key(attr
):
319 return self
._values
[attr
]
321 raise AttributeError, "object '%s' has no attribute '%s'" \
322 % (self
.__class
__.__name
__, attr
)
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
)
332 # must be SimObject param
333 param
= self
._params
.get(attr
, None)
335 # It's ok: set attribute by delegating to 'object' class.
337 value
= param
.convert(value
)
339 msg
= "%s\nError setting param %s.%s to %s\n" % \
340 (e
, self
.__class
__.__name
__, attr
, value
)
343 # I would love to get rid of this
344 elif isSimObject(value
) or isSimObjSequence(value
):
347 raise AttributeError, "Class %s has no parameter %s" \
348 % (self
.__class
__.__name
__, attr
)
350 # clear out old child with this name, if any
351 self
.clear_child(attr
)
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
)]
359 self
._values
[attr
] = value
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
):
366 raise TypeError, "Non-zero index '%s' to SimObject" % key
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
):
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
]
378 def add_child(self
, name
, value
):
379 self
._children
[name
] = value
381 def set_path(self
, parent
, name
):
382 if not hasattr(self
, '_parent'):
383 self
._parent
= parent
385 parent
.add_child(name
, self
)
388 if not hasattr(self
, '_parent'):
390 ppath
= self
._parent
.path()
393 return ppath
+ "." + self
._name
401 def find_any(self
, ptype
):
402 if isinstance(self
, ptype
):
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
)
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
423 def unproxy(self
, base
):
427 print '[' + self
.path() + ']' # .ini section header
429 if hasattr(self
, 'type') and not isinstance(self
, ParamContext
):
430 print 'type=%s' % self
.type
432 child_names
= self
._children
.keys()
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
)
439 param_names
= self
._params
.keys()
441 for param
in param_names
:
442 value
= self
._values
.get(param
, None)
446 value
= value
.unproxy(self
)
448 print >> sys
.stderr
, \
449 "Error in unproxying param '%s' of %s" % \
452 setattr(self
, param
, value
)
453 print '%s=%s' % (param
, self
._values
[param
].ini_str())
455 print # blank line between objects
457 for child
in child_names
:
458 self
._children
[child
].print_ini()
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
468 # instantiate children in same order they were added for
469 # backward compatibility (else we can end up with cpu1
471 for c
in self
.children
:
472 dot
.add_edge(pydot
.Edge(self
.path
,c
.path
, style
="bold"))
475 for param
in self
.params
:
477 if param
.value
is None:
478 raise AttributeError, 'Parameter with no value'
481 string
= param
.string(value
)
483 msg
= 'exception in %s:%s\n%s' % (self
.name
, param
.name
, e
)
487 if isSimObject(param
.ptype
) and string
!= "Null":
488 simobjs
.append(string
)
490 label
+= '%s = %s\\n' % (param
.name
, string
)
493 label
+= "|<%s> %s" % (so
, so
)
494 dot
.add_edge(pydot
.Edge("%s:%s" % (self
.path
, so
), so
,
497 dot
.add_node(pydot
.Node(self
.path
,shape
="Mrecord",label
=label
))
499 # recursively dump out children
500 for c
in self
.children
:
503 class ParamContext(SimObject
):
506 #####################################################################
508 # Proxy object support.
510 #####################################################################
512 class 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
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
)
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
530 # support chained multipliers
531 self
._multiplier
*= other
536 def _mulcheck(self
, result
):
537 if self
._multiplier
== None:
539 return result
* self
._multiplier
541 def unproxy(self
, base
):
545 if self
._search
_self
:
546 result
, done
= self
.find(obj
)
550 try: obj
= obj
._parent
553 result
, done
= self
.find(obj
)
556 raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \
557 (self
.path(), base
.path())
559 if isinstance(result
, BaseProxy
):
561 raise RuntimeError, "Cycle in unproxy"
562 result
= result
.unproxy(obj
)
564 return self
._mulcheck
(result
)
566 def getindex(obj
, index
):
574 # if index is 0 and item is not subscriptable, just
575 # use item itself (so cpu[0] works on uniprocessors)
577 getindex
= staticmethod(getindex
)
579 def set_param_desc(self
, pdesc
):
582 class AttrProxy(BaseProxy
):
583 def __init__(self
, search_self
, search_up
, attr
):
584 super(AttrProxy
, self
).__init
__(search_self
, search_up
)
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
)
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
)
606 val
= getattr(obj
, self
._attr
)
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):
617 assert("Item must be string or integer")
619 val
= val
.unproxy(obj
)
624 for m
in self
._modifiers
:
625 if isinstance(m
, str):
627 elif isinstance(m
, int):
630 assert("Item must be string or integer")
633 class AnyProxy(BaseProxy
):
635 return obj
.find_any(self
._pdesc
.ptype
)
641 if isinstance(obj
, (BaseProxy
, EthernetAddr
)):
643 elif isinstance(obj
, (list, tuple)):
649 class ProxyFactory(object):
650 def __init__(self
, search_self
, search_up
):
651 self
.search_self
= search_self
652 self
.search_up
= search_up
654 def __getattr__(self
, attr
):
656 return AnyProxy(self
.search_self
, self
.search_up
)
658 return AttrProxy(self
.search_self
, self
.search_up
, attr
)
660 # global objects for handling proxies
661 Parent
= ProxyFactory(search_self
= False, search_up
= True)
662 Self
= ProxyFactory(search_self
= True, search_up
= False)
664 #####################################################################
666 # Parameter description classes
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.
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.
679 #####################################################################
681 # Dummy base class to identify types that are legitimate for SimObject
683 class ParamValue(object):
685 # default for printing to .ini file is regular string conversion.
686 # will be overridden in some cases
690 # allows us to blithely call unproxy() on things without checking
691 # if they're really proxies or not
692 def unproxy(self
, base
):
695 # Regular parameter description.
696 class 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
707 self
.default
= args
[0]
710 raise TypeError, 'too many arguments'
712 if kwargs
.has_key('desc'):
713 assert(not hasattr(self
, 'desc'))
714 self
.desc
= kwargs
['desc']
717 if kwargs
.has_key('default'):
718 assert(not hasattr(self
, 'default'))
719 self
.default
= kwargs
['default']
720 del kwargs
['default']
723 raise TypeError, 'extra unknown kwargs %s' % kwargs
725 if not hasattr(self
, 'desc'):
726 raise TypeError, 'desc attribute missing'
728 def __getattr__(self
, attr
):
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
)
738 raise AttributeError, "'%s' object has no attribute '%s'" % \
739 (type(self
).__name
__, attr
)
741 def convert(self
, value
):
742 if isinstance(value
, BaseProxy
):
743 value
.set_param_desc(self
)
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
749 if isinstance(value
, self
.ptype
):
751 if isNullPointer(value
) and issubclass(self
.ptype
, SimObject
):
753 return self
.ptype(value
)
755 # Vector-valued parameter description. Just like ParamDesc, except
756 # that the value is a vector (list) of the specified type instead of a
759 class VectorParamValue(list):
761 return ' '.join([v
.ini_str() for v
in self
])
763 def unproxy(self
, base
):
764 return [v
.unproxy(base
) for v
in self
]
766 class SimObjVector(VectorParamValue
):
771 class 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
)
781 return VectorParamValue(tmp_list
)
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
)
788 class 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
793 def __getattr__(self
, attr
):
795 attr
= self
.ptype_str
+ '.' + attr
796 return ParamFactory(self
.param_desc_class
, attr
)
798 # E.g., Param.Int(5, "number of widgets")
799 def __call__(self
, *args
, **kwargs
):
800 caller_frame
= inspect
.currentframe().f_back
803 ptype
= eval(self
.ptype_str
,
804 caller_frame
.f_globals
, caller_frame
.f_locals
)
805 if not isinstance(ptype
, type):
807 "Param qualifier is not a type: %s" % ptype
809 # if name isn't defined yet, assume it's a SimObject, and
810 # try to resolve it later
812 return self
.param_desc_class(self
.ptype_str
, ptype
, *args
, **kwargs
)
814 Param
= ParamFactory(ParamDesc
)
815 VectorParam
= ParamFactory(VectorParamDesc
)
817 #####################################################################
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.
829 #####################################################################
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.
834 class NumericParamValue(ParamValue
):
836 return str(self
.value
)
839 return float(self
.value
)
841 # hook for bounds checking
845 def __mul__(self
, other
):
846 newobj
= self
.__class
__(self
)
847 newobj
.value
*= other
853 def __div__(self
, other
):
854 newobj
= self
.__class
__(self
)
855 newobj
.value
/= other
859 def __sub__(self
, other
):
860 newobj
= self
.__class
__(self
)
861 newobj
.value
-= other
865 class Range(ParamValue
):
866 type = int # default; can be overridden in subclasses
867 def __init__(self
, *args
, **kwargs
):
869 def handle_kwargs(self
, 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
875 raise TypeError, "Either end or size must be specified"
878 self
.first
= self
.type(kwargs
.pop('start'))
879 handle_kwargs(self
, 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
)
889 self
.first
= self
.type(0)
890 self
.second
= self
.type(args
[0]) - 1
893 self
.first
= self
.type(args
[0])
894 self
.second
= self
.type(args
[1])
896 raise TypeError, "Too many arguments specified"
899 raise TypeError, "too many keywords: %s" % kwargs
.keys()
902 return '%s:%s' % (self
.first
, self
.second
)
904 # Metaclass for bounds-checked integer parameters. See CheckedInt.
905 class CheckedIntType(type):
906 def __init__(cls
, name
, bases
, dict):
907 super(CheckedIntType
, cls
).__init
__(name
, bases
, dict)
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':
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" \
922 cls
.max = 2 ** cls
.size
- 1
924 cls
.min = -(2 ** (cls
.size
- 1))
925 cls
.max = (2 ** (cls
.size
- 1)) - 1
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__.
931 class CheckedInt(NumericParamValue
):
932 __metaclass__
= CheckedIntType
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)
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
)
946 class Int(CheckedInt
): size
= 32; unsigned
= False
947 class Unsigned(CheckedInt
): size
= 32; unsigned
= True
949 class Int8(CheckedInt
): size
= 8; unsigned
= False
950 class UInt8(CheckedInt
): size
= 8; unsigned
= True
951 class Int16(CheckedInt
): size
= 16; unsigned
= False
952 class UInt16(CheckedInt
): size
= 16; unsigned
= True
953 class Int32(CheckedInt
): size
= 32; unsigned
= False
954 class UInt32(CheckedInt
): size
= 32; unsigned
= True
955 class Int64(CheckedInt
): size
= 64; unsigned
= False
956 class UInt64(CheckedInt
): size
= 64; unsigned
= True
958 class Counter(CheckedInt
): size
= 64; unsigned
= True
959 class Tick(CheckedInt
): size
= 64; unsigned
= True
960 class TcpPort(CheckedInt
): size
= 16; unsigned
= True
961 class UdpPort(CheckedInt
): size
= 16; unsigned
= True
963 class Percent(CheckedInt
): min = 0; max = 100
965 class Float(ParamValue
, float):
968 class MemorySize(CheckedInt
):
971 def __init__(self
, value
):
972 if isinstance(value
, MemorySize
):
973 self
.value
= value
.value
975 self
.value
= toMemorySize(value
)
978 class MemorySize32(CheckedInt
):
981 def __init__(self
, value
):
982 if isinstance(value
, MemorySize
):
983 self
.value
= value
.value
985 self
.value
= toMemorySize(value
)
988 class Addr(CheckedInt
):
991 def __init__(self
, value
):
992 if isinstance(value
, Addr
):
993 self
.value
= value
.value
996 self
.value
= toMemorySize(value
)
998 self
.value
= long(value
)
1001 class AddrRange(Range
):
1004 # String-valued parameter. Just mixin the ParamValue class
1005 # with the built-in str class.
1006 class String(ParamValue
,str):
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.
1012 class Bool(ParamValue
):
1013 def __init__(self
, value
):
1015 self
.value
= toBool(value
)
1017 self
.value
= bool(value
)
1020 return str(self
.value
)
1027 def IncEthernetAddr(addr
, val
= 1):
1028 bytes
= map(lambda x
: int(x
, 16), addr
.split(':'))
1030 for i
in (5, 4, 3, 2, 1):
1031 val
,rem
= divmod(bytes
[i
], 256)
1036 assert(bytes
[0] <= 255)
1037 return ':'.join(map(lambda x
: '%02x' % x
, bytes
))
1039 class NextEthernetAddr(object):
1040 addr
= "00:90:00:00:00:01"
1042 def __init__(self
, inc
= 1):
1043 self
.value
= NextEthernetAddr
.addr
1044 NextEthernetAddr
.addr
= IncEthernetAddr(NextEthernetAddr
.addr
, inc
)
1046 class EthernetAddr(ParamValue
):
1047 def __init__(self
, value
):
1048 if value
== NextEthernetAddr
:
1052 if not isinstance(value
, str):
1053 raise TypeError, "expected an ethernet address and didn't get one"
1055 bytes
= value
.split(':')
1057 raise TypeError, 'invalid ethernet address %s' % value
1060 if not 0 <= int(byte
) <= 256:
1061 raise TypeError, 'invalid ethernet address %s' % value
1065 def unproxy(self
, base
):
1066 if self
.value
== NextEthernetAddr
:
1067 self
.addr
= self
.value().value
1071 if self
.value
== NextEthernetAddr
:
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
1080 class NullSimObject(object):
1081 __metaclass__
= Singleton
1086 def _instantiate(self
, parent
= None, path
= ''):
1092 def unproxy(self
, base
):
1095 def set_path(self
, parent
, name
):
1100 # The only instance you'll ever need...
1101 Null
= NULL
= NullSimObject()
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.)
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.
1118 # Metaclass for Enum types
1119 class 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()
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
1134 for idx
,val
in enumerate(cls
.vals
):
1137 raise TypeError, "Enum-derived class must define "\
1138 "attribute 'map' or 'vals'"
1140 super(MetaEnum
, cls
).__init
__(name
, bases
, init_dict
)
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
])
1148 # Base class for enum types.
1149 class Enum(ParamValue
):
1150 __metaclass__
= MetaEnum
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
)
1162 ticks_per_sec
= None
1164 # how big does a rounding error need to be before we warn about it?
1165 frequency_tolerance
= 0.001 # 0.1%
1167 # convert a floting-point # of ticks to integer, and warn if rounding
1168 # discards too much precision
1169 def tick_check(float_ticks
):
1170 if float_ticks
== 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
)
1180 def getLatency(value
):
1181 if isinstance(value
, Latency
) or isinstance(value
, Clock
):
1183 elif isinstance(value
, Frequency
) or isinstance(value
, RootClock
):
1184 return 1 / value
.value
1185 elif isinstance(value
, str):
1187 return toLatency(value
)
1190 return 1 / toFrequency(value
)
1193 raise ValueError, "Invalid Frequency/Latency value '%s'" % value
1196 class Latency(NumericParamValue
):
1197 def __init__(self
, value
):
1198 self
.value
= getLatency(value
)
1200 def __getattr__(self
, attr
):
1201 if attr
in ('latency', 'period'):
1203 if attr
== 'frequency':
1204 return Frequency(self
)
1205 raise AttributeError, "Latency object has no attribute '%s'" % attr
1207 # convert latency to ticks
1209 return str(tick_check(self
.value
* ticks_per_sec
))
1211 class Frequency(NumericParamValue
):
1212 def __init__(self
, value
):
1213 self
.value
= 1 / getLatency(value
)
1215 def __getattr__(self
, attr
):
1216 if attr
== 'frequency':
1218 if attr
in ('latency', 'period'):
1219 return Latency(self
)
1220 raise AttributeError, "Frequency object has no attribute '%s'" % attr
1222 # convert frequency to ticks per period
1224 return self
.period
.ini_str()
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.
1229 class RootClock(ParamValue
):
1230 def __init__(self
, value
):
1231 self
.value
= 1 / getLatency(value
)
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
1241 return str(tick_check(self
.value
))
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.
1246 class Clock(ParamValue
):
1247 def __init__(self
, value
):
1248 self
.value
= getLatency(value
)
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
1258 return self
.period
.ini_str()
1260 class NetworkBandwidth(float,ParamValue
):
1261 def __new__(cls
, value
):
1262 val
= toNetworkBandwidth(value
) / 8.0
1263 return super(cls
, NetworkBandwidth
).__new
__(cls
, val
)
1266 return str(self
.val
)
1269 return '%f' % (ticks_per_sec
/ float(self
))
1271 class MemoryBandwidth(float,ParamValue
):
1272 def __new__(self
, value
):
1273 val
= toMemoryBandwidth(value
)
1274 return super(cls
, MemoryBandwidth
).__new
__(cls
, val
)
1277 return str(self
.val
)
1280 return '%f' % (ticks_per_sec
/ float(self
))
1283 # "Constants"... handy aliases for various values.
1286 # Some memory range specifications use this as a default upper bound.
1289 AllMemory
= AddrRange(0, MaxAddr
)
1291 #####################################################################
1293 # The final hook to generate .ini files. Called from configuration
1294 # script once config is built.
1295 def instantiate(root
):
1296 global ticks_per_sec
1297 ticks_per_sec
= float(root
.clock
.frequency
)
1299 noDot
= True # temporary until we fix dot
1302 instance
.outputDot(dot
)
1303 dot
.orientation
= "portrait"
1305 dot
.ranksep
="equally"
1307 dot
.write("config.dot")
1308 dot
.write_ps("config.ps")
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',
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',
1325 'NextEthernetAddr', 'instantiate']