1 # Copyright (c) 2004-2006 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 import os
, re
, sys
, types
, inspect
, copy
33 from m5
import panic
, cc_main
35 from multidict
import multidict
43 class Singleton(type):
44 def __call__(cls
, *args
, **kwargs
):
45 if hasattr(cls
, '_instance'):
48 cls
._instance
= super(Singleton
, cls
).__call
__(*args
, **kwargs
)
51 #####################################################################
53 # M5 Python Configuration Utility
55 # The basic idea is to write simple Python programs that build Python
56 # objects corresponding to M5 SimObjects for the desired simulation
57 # configuration. For now, the Python emits a .ini file that can be
58 # parsed by M5. In the future, some tighter integration between M5
59 # and the Python interpreter may allow bypassing the .ini file.
61 # Each SimObject class in M5 is represented by a Python class with the
62 # same name. The Python inheritance tree mirrors the M5 C++ tree
63 # (e.g., SimpleCPU derives from BaseCPU in both cases, and all
64 # SimObjects inherit from a single SimObject base class). To specify
65 # an instance of an M5 SimObject in a configuration, the user simply
66 # instantiates the corresponding Python object. The parameters for
67 # that SimObject are given by assigning to attributes of the Python
68 # object, either using keyword assignment in the constructor or in
69 # separate assignment statements. For example:
71 # cache = BaseCache(size='64KB')
72 # cache.hit_latency = 3
75 # The magic lies in the mapping of the Python attributes for SimObject
76 # classes to the actual SimObject parameter specifications. This
77 # allows parameter validity checking in the Python code. Continuing
78 # the example above, the statements "cache.blurfl=3" or
79 # "cache.assoc='hello'" would both result in runtime errors in Python,
80 # since the BaseCache object has no 'blurfl' parameter and the 'assoc'
81 # parameter requires an integer, respectively. This magic is done
82 # primarily by overriding the special __setattr__ method that controls
83 # assignment to object attributes.
85 # Once a set of Python objects have been instantiated in a hierarchy,
86 # calling 'instantiate(obj)' (where obj is the root of the hierarchy)
87 # will generate a .ini file.
89 #####################################################################
91 # dict to look up SimObjects based on path
94 #############################
98 #############################
100 def isSimObject(value
):
101 return isinstance(value
, SimObject
)
103 def isSimObjectSequence(value
):
104 if not isinstance(value
, (list, tuple)) or len(value
) == 0:
108 if not isNullPointer(val
) and not isSimObject(val
):
113 def isSimObjectOrSequence(value
):
114 return isSimObject(value
) or isSimObjectSequence(value
)
116 def isNullPointer(value
):
117 return isinstance(value
, NullSimObject
)
119 # Apply method to object.
120 # applyMethod(obj, 'meth', <args>) is equivalent to obj.meth(<args>)
121 def applyMethod(obj
, meth
, *args
, **kwargs
):
122 return getattr(obj
, meth
)(*args
, **kwargs
)
124 # If the first argument is an (non-sequence) object, apply the named
125 # method with the given arguments. If the first argument is a
126 # sequence, apply the method to each element of the sequence (a la
128 def applyOrMap(objOrSeq
, meth
, *args
, **kwargs
):
129 if not isinstance(objOrSeq
, (list, tuple)):
130 return applyMethod(objOrSeq
, meth
, *args
, **kwargs
)
132 return [applyMethod(o
, meth
, *args
, **kwargs
) for o
in objOrSeq
]
135 # The metaclass for SimObject. This class controls how new classes
136 # that derive from SimObject are instantiated, and provides inherited
137 # class behavior (just like a class controls how instances of that
138 # class are instantiated, and provides inherited instance behavior).
139 class MetaSimObject(type):
140 # Attributes that can be set only at initialization time
141 init_keywords
= { 'abstract' : types
.BooleanType
,
142 'type' : types
.StringType
}
143 # Attributes that can be set any time
144 keywords
= { 'check' : types
.FunctionType
}
146 # __new__ is called before __init__, and is where the statements
147 # in the body of the class definition get loaded into the class's
148 # __dict__. We intercept this to filter out parameter & port assignments
149 # and only allow "private" attributes to be passed to the base
150 # __new__ (starting with underscore).
151 def __new__(mcls
, name
, bases
, dict):
152 # Copy "private" attributes, functions, and classes to the
153 # official dict. Everything else goes in _init_dict to be
154 # filtered in __init__.
157 for key
,val
in dict.items():
158 if key
.startswith('_') or isinstance(val
, (types
.FunctionType
,
162 # must be a param/port setting
163 value_dict
[key
] = val
164 cls_dict
['_value_dict'] = value_dict
165 return super(MetaSimObject
, mcls
).__new
__(mcls
, name
, bases
, cls_dict
)
167 # subclass initialization
168 def __init__(cls
, name
, bases
, dict):
169 # calls type.__init__()... I think that's a no-op, but leave
170 # it here just in case it's not.
171 super(MetaSimObject
, cls
).__init
__(name
, bases
, dict)
173 # initialize required attributes
175 # class-only attributes
176 cls
._params
= multidict() # param descriptions
177 cls
._ports
= multidict() # port descriptions
179 # class or instance attributes
180 cls
._values
= multidict() # param values
181 cls
._port
_map
= multidict() # port bindings
182 cls
._instantiated
= False # really instantiated, cloned, or subclassed
184 # We don't support multiple inheritance. If you want to, you
185 # must fix multidict to deal with it properly.
187 raise TypeError, "SimObjects do not support multiple inheritance"
191 # Set up general inheritance via multidicts. A subclass will
192 # inherit all its settings from the base class. The only time
193 # the following is not true is when we define the SimObject
194 # class itself (in which case the multidicts have no parent).
195 if isinstance(base
, MetaSimObject
):
196 cls
._params
.parent
= base
._params
197 cls
._ports
.parent
= base
._ports
198 cls
._values
.parent
= base
._values
199 cls
._port
_map
.parent
= base
._port
_map
200 # mark base as having been subclassed
201 base
._instantiated
= True
203 # Now process the _value_dict items. They could be defining
204 # new (or overriding existing) parameters or ports, setting
205 # class keywords (e.g., 'abstract'), or setting parameter
206 # values or port bindings. The first 3 can only be set when
207 # the class is defined, so we handle them here. The others
208 # can be set later too, so just emulate that by calling
210 for key
,val
in cls
._value
_dict
.items():
212 if isinstance(val
, ParamDesc
):
213 cls
._new
_param
(key
, val
)
216 elif isinstance(val
, Port
):
217 cls
._ports
[key
] = val
219 # init-time-only keywords
220 elif cls
.init_keywords
.has_key(key
):
221 cls
._set
_keyword
(key
, val
, cls
.init_keywords
[key
])
223 # default: use normal path (ends up in __setattr__)
225 setattr(cls
, key
, val
)
227 def _set_keyword(cls
, keyword
, val
, kwtype
):
228 if not isinstance(val
, kwtype
):
229 raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
230 (keyword
, type(val
), kwtype
)
231 if isinstance(val
, types
.FunctionType
):
232 val
= classmethod(val
)
233 type.__setattr
__(cls
, keyword
, val
)
235 def _new_param(cls
, name
, value
):
236 cls
._params
[name
] = value
237 if hasattr(value
, 'default'):
238 setattr(cls
, name
, value
.default
)
240 # Set attribute (called on foo.attr = value when foo is an
241 # instance of class cls).
242 def __setattr__(cls
, attr
, value
):
243 # normal processing for private attributes
244 if attr
.startswith('_'):
245 type.__setattr
__(cls
, attr
, value
)
248 if cls
.keywords
.has_key(attr
):
249 cls
._set
_keyword
(attr
, value
, cls
.keywords
[attr
])
252 if cls
._ports
.has_key(attr
):
253 self
._ports
[attr
].connect(self
, attr
, value
)
256 if isSimObjectOrSequence(value
) and cls
._instantiated
:
257 raise RuntimeError, \
258 "cannot set SimObject parameter '%s' after\n" \
259 " class %s has been instantiated or subclassed" \
260 % (attr
, cls
.__name
__)
263 param
= cls
._params
.get(attr
, None)
266 cls
._values
[attr
] = param
.convert(value
)
268 msg
= "%s\nError setting param %s.%s to %s\n" % \
269 (e
, cls
.__name
__, attr
, value
)
272 elif isSimObjectOrSequence(value
):
273 # if RHS is a SimObject, it's an implicit child assignment
274 cls
._values
[attr
] = value
276 raise AttributeError, \
277 "Class %s has no parameter %s" % (cls
.__name
__, attr
)
279 def __getattr__(cls
, attr
):
280 if cls
._values
.has_key(attr
):
281 return cls
._values
[attr
]
283 raise AttributeError, \
284 "object '%s' has no attribute '%s'" % (cls
.__name
__, attr
)
286 # The SimObject class is the root of the special hierarchy. Most of
287 # the code in this class deals with the configuration hierarchy itself
288 # (parent/child node relationships).
289 class SimObject(object):
290 # Specify metaclass. Any class inheriting from SimObject will
291 # get this metaclass.
292 __metaclass__
= MetaSimObject
294 # Initialize new instance. For objects with SimObject-valued
295 # children, we need to recursively clone the classes represented
296 # by those param values as well in a consistent "deep copy"-style
297 # fashion. That is, we want to make sure that each instance is
298 # cloned only once, and that if there are multiple references to
299 # the same original object, we end up with the corresponding
300 # cloned references all pointing to the same cloned instance.
301 def __init__(self
, **kwargs
):
302 ancestor
= kwargs
.get('_ancestor')
303 memo_dict
= kwargs
.get('_memo')
304 if memo_dict
is None:
305 # prepare to memoize any recursively instantiated objects
308 # memoize me now to avoid problems with recursive calls
309 memo_dict
[ancestor
] = self
312 ancestor
= self
.__class
__
313 ancestor
._instantiated
= True
315 # initialize required attributes
318 self
._ccObject
= None # pointer to C++ object
319 self
._instantiated
= False # really "cloned"
321 # Inherit parameter values from class using multidict so
322 # individual value settings can be overridden.
323 self
._values
= multidict(ancestor
._values
)
324 # clone SimObject-valued parameters
325 for key
,val
in ancestor
._values
.iteritems():
327 setattr(self
, key
, val(_memo
=memo_dict
))
328 elif isSimObjectSequence(val
) and len(val
):
329 setattr(self
, key
, [ v(_memo
=memo_dict
) for v
in val
])
330 # clone port references. no need to use a multidict here
331 # since we will be creating new references for all ports.
333 for key
,val
in ancestor
._port
_map
.iteritems():
334 self
._port
_map
[key
] = applyOrMap(val
, 'clone', memo_dict
)
335 # apply attribute assignments from keyword args, if any
336 for key
,val
in kwargs
.iteritems():
337 setattr(self
, key
, val
)
339 # "Clone" the current instance by creating another instance of
340 # this instance's class, but that inherits its parameter values
341 # and port mappings from the current instance. If we're in a
342 # "deep copy" recursive clone, check the _memo dict to see if
343 # we've already cloned this instance.
344 def __call__(self
, **kwargs
):
345 memo_dict
= kwargs
.get('_memo')
346 if memo_dict
is None:
347 # no memo_dict: must be top-level clone operation.
348 # this is only allowed at the root of a hierarchy
350 raise RuntimeError, "attempt to clone object %s " \
351 "not at the root of a tree (parent = %s)" \
352 % (self
, self
._parent
)
353 # create a new dict and use that.
355 kwargs
['_memo'] = memo_dict
356 elif memo_dict
.has_key(self
):
357 # clone already done & memoized
358 return memo_dict
[self
]
359 return self
.__class
__(_ancestor
= self
, **kwargs
)
361 def __getattr__(self
, attr
):
362 if self
._ports
.has_key(attr
):
363 # return reference that can be assigned to another port
365 return self
._ports
[attr
].makeRef(self
, attr
)
367 if self
._values
.has_key(attr
):
368 return self
._values
[attr
]
370 raise AttributeError, "object '%s' has no attribute '%s'" \
371 % (self
.__class
__.__name
__, attr
)
373 # Set attribute (called on foo.attr = value when foo is an
374 # instance of class cls).
375 def __setattr__(self
, attr
, value
):
376 # normal processing for private attributes
377 if attr
.startswith('_'):
378 object.__setattr
__(self
, attr
, value
)
381 if self
._ports
.has_key(attr
):
382 # set up port connection
383 self
._ports
[attr
].connect(self
, attr
, value
)
386 if isSimObjectOrSequence(value
) and self
._instantiated
:
387 raise RuntimeError, \
388 "cannot set SimObject parameter '%s' after\n" \
389 " instance been cloned %s" % (attr
, `self`
)
391 # must be SimObject param
392 param
= self
._params
.get(attr
, None)
395 value
= param
.convert(value
)
397 msg
= "%s\nError setting param %s.%s to %s\n" % \
398 (e
, self
.__class
__.__name
__, attr
, value
)
401 elif isSimObjectOrSequence(value
):
404 raise AttributeError, "Class %s has no parameter %s" \
405 % (self
.__class
__.__name
__, attr
)
407 # clear out old child with this name, if any
408 self
.clear_child(attr
)
410 if isSimObject(value
):
411 value
.set_path(self
, attr
)
412 elif isSimObjectSequence(value
):
413 value
= SimObjVector(value
)
414 [v
.set_path(self
, "%s%d" % (attr
, i
)) for i
,v
in enumerate(value
)]
416 self
._values
[attr
] = value
418 # this hack allows tacking a '[0]' onto parameters that may or may
419 # not be vectors, and always getting the first element (e.g. cpus)
420 def __getitem__(self
, key
):
423 raise TypeError, "Non-zero index '%s' to SimObject" % key
425 # clear out children with given name, even if it's a vector
426 def clear_child(self
, name
):
427 if not self
._children
.has_key(name
):
429 child
= self
._children
[name
]
430 if isinstance(child
, SimObjVector
):
431 for i
in xrange(len(child
)):
432 del self
._children
["s%d" % (name
, i
)]
433 del self
._children
[name
]
435 def add_child(self
, name
, value
):
436 self
._children
[name
] = value
438 def set_path(self
, parent
, name
):
440 self
._parent
= parent
442 parent
.add_child(name
, self
)
447 ppath
= self
._parent
.path()
450 return ppath
+ "." + self
._name
458 def find_any(self
, ptype
):
459 if isinstance(self
, ptype
):
463 for child
in self
._children
.itervalues():
464 if isinstance(child
, ptype
):
465 if found_obj
!= None and child
!= found_obj
:
466 raise AttributeError, \
467 'parent.any matched more than one: %s %s' % \
468 (found_obj
.path
, child
.path
)
471 for pname
,pdesc
in self
._params
.iteritems():
472 if issubclass(pdesc
.ptype
, ptype
):
473 match_obj
= self
._values
[pname
]
474 if found_obj
!= None and found_obj
!= match_obj
:
475 raise AttributeError, \
476 'parent.any matched more than one: %s' % obj
.path
477 found_obj
= match_obj
478 return found_obj
, found_obj
!= None
480 def unproxy(self
, base
):
484 print '[' + self
.path() + ']' # .ini section header
486 instanceDict
[self
.path()] = self
488 if hasattr(self
, 'type') and not isinstance(self
, ParamContext
):
489 print 'type=%s' % self
.type
491 child_names
= self
._children
.keys()
493 np_child_names
= [c
for c
in child_names \
494 if not isinstance(self
._children
[c
], ParamContext
)]
495 if len(np_child_names
):
496 print 'children=%s' % ' '.join(np_child_names
)
498 param_names
= self
._params
.keys()
500 for param
in param_names
:
501 value
= self
._values
.get(param
, None)
505 value
= value
.unproxy(self
)
507 print >> sys
.stderr
, \
508 "Error in unproxying param '%s' of %s" % \
511 setattr(self
, param
, value
)
512 print '%s=%s' % (param
, self
._values
[param
].ini_str())
514 print # blank line between objects
516 for child
in child_names
:
517 self
._children
[child
].print_ini()
519 # Call C++ to create C++ object corresponding to this object and
520 # (recursively) all its children
521 def createCCObject(self
):
522 self
.getCCObject() # force creation
523 for child
in self
._children
.itervalues():
524 child
.createCCObject()
526 # Get C++ object corresponding to this object, calling C++ if
527 # necessary to construct it. Does *not* recursively create
529 def getCCObject(self
):
530 if not self
._ccObject
:
531 self
._ccObject
= -1 # flag to catch cycles in recursion
532 self
._ccObject
= cc_main
.createSimObject(self
.path())
533 elif self
._ccObject
== -1:
534 raise RuntimeError, "%s: recursive call to getCCObject()" \
536 return self
._ccObject
538 # Create C++ port connections corresponding to the connections in
539 # _port_map (& recursively for all children)
540 def connectPorts(self
):
541 for portRef
in self
._port
_map
.itervalues():
542 applyOrMap(portRef
, 'ccConnect')
543 for child
in self
._children
.itervalues():
546 def startQuiesce(self
, quiesce_event
, recursive
):
548 # ParamContexts don't serialize
549 if isinstance(self
, SimObject
) and not isinstance(self
, ParamContext
):
550 if self
._ccObject
.quiesce(quiesce_event
):
553 for child
in self
._children
.itervalues():
554 count
+= child
.startQuiesce(quiesce_event
, True)
558 if isinstance(self
, SimObject
) and not isinstance(self
, ParamContext
):
559 self
._ccObject
.resume()
560 for child
in self
._children
.itervalues():
563 def changeTiming(self
, mode
):
564 if isinstance(self
, SimObject
) and not isinstance(self
, ParamContext
):
565 self
._ccObject
.setMemoryMode(mode
)
566 for child
in self
._children
.itervalues():
567 child
.changeTiming(mode
)
569 def takeOverFrom(self
, old_cpu
):
570 cpu_ptr
= cc_main
.convertToBaseCPUPtr(old_cpu
._ccObject
)
571 self
._ccObject
.takeOverFrom(cpu_ptr
)
573 # generate output file for 'dot' to display as a pretty graph.
574 # this code is currently broken.
575 def outputDot(self
, dot
):
576 label
= "{%s|" % self
.path
577 if isSimObject(self
.realtype
):
578 label
+= '%s|' % self
.type
581 # instantiate children in same order they were added for
582 # backward compatibility (else we can end up with cpu1
584 for c
in self
.children
:
585 dot
.add_edge(pydot
.Edge(self
.path
,c
.path
, style
="bold"))
588 for param
in self
.params
:
590 if param
.value
is None:
591 raise AttributeError, 'Parameter with no value'
594 string
= param
.string(value
)
596 msg
= 'exception in %s:%s\n%s' % (self
.name
, param
.name
, e
)
600 if isSimObject(param
.ptype
) and string
!= "Null":
601 simobjs
.append(string
)
603 label
+= '%s = %s\\n' % (param
.name
, string
)
606 label
+= "|<%s> %s" % (so
, so
)
607 dot
.add_edge(pydot
.Edge("%s:%s" % (self
.path
, so
), so
,
610 dot
.add_node(pydot
.Node(self
.path
,shape
="Mrecord",label
=label
))
612 # recursively dump out children
613 for c
in self
.children
:
616 class ParamContext(SimObject
):
619 #####################################################################
621 # Proxy object support.
623 #####################################################################
625 class BaseProxy(object):
626 def __init__(self
, search_self
, search_up
):
627 self
._search
_self
= search_self
628 self
._search
_up
= search_up
629 self
._multiplier
= None
631 def __setattr__(self
, attr
, value
):
632 if not attr
.startswith('_'):
633 raise AttributeError, 'cannot set attribute on proxy object'
634 super(BaseProxy
, self
).__setattr
__(attr
, value
)
636 # support multiplying proxies by constants
637 def __mul__(self
, other
):
638 if not isinstance(other
, (int, long, float)):
639 raise TypeError, "Proxy multiplier must be integer"
640 if self
._multiplier
== None:
641 self
._multiplier
= other
643 # support chained multipliers
644 self
._multiplier
*= other
649 def _mulcheck(self
, result
):
650 if self
._multiplier
== None:
652 return result
* self
._multiplier
654 def unproxy(self
, base
):
658 if self
._search
_self
:
659 result
, done
= self
.find(obj
)
666 result
, done
= self
.find(obj
)
669 raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \
670 (self
.path(), base
.path())
672 if isinstance(result
, BaseProxy
):
674 raise RuntimeError, "Cycle in unproxy"
675 result
= result
.unproxy(obj
)
677 return self
._mulcheck
(result
)
679 def getindex(obj
, index
):
687 # if index is 0 and item is not subscriptable, just
688 # use item itself (so cpu[0] works on uniprocessors)
690 getindex
= staticmethod(getindex
)
692 def set_param_desc(self
, pdesc
):
695 class AttrProxy(BaseProxy
):
696 def __init__(self
, search_self
, search_up
, attr
):
697 super(AttrProxy
, self
).__init
__(search_self
, search_up
)
701 def __getattr__(self
, attr
):
702 # python uses __bases__ internally for inheritance
703 if attr
.startswith('_'):
704 return super(AttrProxy
, self
).__getattr
__(self
, attr
)
705 if hasattr(self
, '_pdesc'):
706 raise AttributeError, "Attribute reference on bound proxy"
707 self
._modifiers
.append(attr
)
710 # support indexing on proxies (e.g., Self.cpu[0])
711 def __getitem__(self
, key
):
712 if not isinstance(key
, int):
713 raise TypeError, "Proxy object requires integer index"
714 self
._modifiers
.append(key
)
719 val
= getattr(obj
, self
._attr
)
723 val
= val
.unproxy(obj
)
724 for m
in self
._modifiers
:
725 if isinstance(m
, str):
726 val
= getattr(val
, m
)
727 elif isinstance(m
, int):
730 assert("Item must be string or integer")
732 val
= val
.unproxy(obj
)
737 for m
in self
._modifiers
:
738 if isinstance(m
, str):
740 elif isinstance(m
, int):
743 assert("Item must be string or integer")
746 class AnyProxy(BaseProxy
):
748 return obj
.find_any(self
._pdesc
.ptype
)
754 if isinstance(obj
, (BaseProxy
, EthernetAddr
)):
756 elif isinstance(obj
, (list, tuple)):
762 class ProxyFactory(object):
763 def __init__(self
, search_self
, search_up
):
764 self
.search_self
= search_self
765 self
.search_up
= search_up
767 def __getattr__(self
, attr
):
769 return AnyProxy(self
.search_self
, self
.search_up
)
771 return AttrProxy(self
.search_self
, self
.search_up
, attr
)
773 # global objects for handling proxies
774 Parent
= ProxyFactory(search_self
= False, search_up
= True)
775 Self
= ProxyFactory(search_self
= True, search_up
= False)
777 #####################################################################
779 # Parameter description classes
781 # The _params dictionary in each class maps parameter names to either
782 # a Param or a VectorParam object. These objects contain the
783 # parameter description string, the parameter type, and the default
784 # value (if any). The convert() method on these objects is used to
785 # force whatever value is assigned to the parameter to the appropriate
788 # Note that the default values are loaded into the class's attribute
789 # space when the parameter dictionary is initialized (in
790 # MetaSimObject._new_param()); after that point they aren't used.
792 #####################################################################
794 # Dummy base class to identify types that are legitimate for SimObject
796 class ParamValue(object):
798 # default for printing to .ini file is regular string conversion.
799 # will be overridden in some cases
803 # allows us to blithely call unproxy() on things without checking
804 # if they're really proxies or not
805 def unproxy(self
, base
):
808 # Regular parameter description.
809 class ParamDesc(object):
810 def __init__(self
, ptype_str
, ptype
, *args
, **kwargs
):
811 self
.ptype_str
= ptype_str
812 # remember ptype only if it is provided
820 self
.default
= args
[0]
823 raise TypeError, 'too many arguments'
825 if kwargs
.has_key('desc'):
826 assert(not hasattr(self
, 'desc'))
827 self
.desc
= kwargs
['desc']
830 if kwargs
.has_key('default'):
831 assert(not hasattr(self
, 'default'))
832 self
.default
= kwargs
['default']
833 del kwargs
['default']
836 raise TypeError, 'extra unknown kwargs %s' % kwargs
838 if not hasattr(self
, 'desc'):
839 raise TypeError, 'desc attribute missing'
841 def __getattr__(self
, attr
):
844 ptype
= eval(self
.ptype_str
, m5
.objects
.__dict
__)
845 if not isinstance(ptype
, type):
846 panic("Param qualifier is not a type: %s" % self
.ptype
)
851 raise AttributeError, "'%s' object has no attribute '%s'" % \
852 (type(self
).__name
__, attr
)
854 def convert(self
, value
):
855 if isinstance(value
, BaseProxy
):
856 value
.set_param_desc(self
)
858 if not hasattr(self
, 'ptype') and isNullPointer(value
):
859 # deferred evaluation of SimObject; continue to defer if
860 # we're just assigning a null pointer
862 if isinstance(value
, self
.ptype
):
864 if isNullPointer(value
) and issubclass(self
.ptype
, SimObject
):
866 return self
.ptype(value
)
868 # Vector-valued parameter description. Just like ParamDesc, except
869 # that the value is a vector (list) of the specified type instead of a
872 class VectorParamValue(list):
874 return ' '.join([v
.ini_str() for v
in self
])
876 def unproxy(self
, base
):
877 return [v
.unproxy(base
) for v
in self
]
879 class SimObjVector(VectorParamValue
):
884 class VectorParamDesc(ParamDesc
):
885 # Convert assigned value to appropriate type. If the RHS is not a
886 # list or tuple, it generates a single-element list.
887 def convert(self
, value
):
888 if isinstance(value
, (list, tuple)):
889 # list: coerce each element into new list
890 tmp_list
= [ ParamDesc
.convert(self
, v
) for v
in value
]
891 if isSimObjectSequence(tmp_list
):
892 return SimObjVector(tmp_list
)
894 return VectorParamValue(tmp_list
)
896 # singleton: leave it be (could coerce to a single-element
897 # list here, but for some historical reason we don't...
898 return ParamDesc
.convert(self
, value
)
901 class ParamFactory(object):
902 def __init__(self
, param_desc_class
, ptype_str
= None):
903 self
.param_desc_class
= param_desc_class
904 self
.ptype_str
= ptype_str
906 def __getattr__(self
, attr
):
908 attr
= self
.ptype_str
+ '.' + attr
909 return ParamFactory(self
.param_desc_class
, attr
)
911 # E.g., Param.Int(5, "number of widgets")
912 def __call__(self
, *args
, **kwargs
):
913 caller_frame
= inspect
.currentframe().f_back
916 ptype
= eval(self
.ptype_str
,
917 caller_frame
.f_globals
, caller_frame
.f_locals
)
918 if not isinstance(ptype
, type):
920 "Param qualifier is not a type: %s" % ptype
922 # if name isn't defined yet, assume it's a SimObject, and
923 # try to resolve it later
925 return self
.param_desc_class(self
.ptype_str
, ptype
, *args
, **kwargs
)
927 Param
= ParamFactory(ParamDesc
)
928 VectorParam
= ParamFactory(VectorParamDesc
)
930 #####################################################################
934 # Though native Python types could be used to specify parameter types
935 # (the 'ptype' field of the Param and VectorParam classes), it's more
936 # flexible to define our own set of types. This gives us more control
937 # over how Python expressions are converted to values (via the
938 # __init__() constructor) and how these values are printed out (via
939 # the __str__() conversion method). Eventually we'll need these types
940 # to correspond to distinct C++ types as well.
942 #####################################################################
944 # superclass for "numeric" parameter values, to emulate math
945 # operations in a type-safe way. e.g., a Latency times an int returns
946 # a new Latency object.
947 class NumericParamValue(ParamValue
):
949 return str(self
.value
)
952 return float(self
.value
)
954 # hook for bounds checking
958 def __mul__(self
, other
):
959 newobj
= self
.__class
__(self
)
960 newobj
.value
*= other
966 def __div__(self
, other
):
967 newobj
= self
.__class
__(self
)
968 newobj
.value
/= other
972 def __sub__(self
, other
):
973 newobj
= self
.__class
__(self
)
974 newobj
.value
-= other
978 class Range(ParamValue
):
979 type = int # default; can be overridden in subclasses
980 def __init__(self
, *args
, **kwargs
):
982 def handle_kwargs(self
, kwargs
):
984 self
.second
= self
.type(kwargs
.pop('end'))
985 elif 'size' in kwargs
:
986 self
.second
= self
.first
+ self
.type(kwargs
.pop('size')) - 1
988 raise TypeError, "Either end or size must be specified"
991 self
.first
= self
.type(kwargs
.pop('start'))
992 handle_kwargs(self
, kwargs
)
996 self
.first
= self
.type(args
[0])
997 handle_kwargs(self
, kwargs
)
998 elif isinstance(args
[0], Range
):
999 self
.first
= self
.type(args
[0].first
)
1000 self
.second
= self
.type(args
[0].second
)
1002 self
.first
= self
.type(0)
1003 self
.second
= self
.type(args
[0]) - 1
1005 elif len(args
) == 2:
1006 self
.first
= self
.type(args
[0])
1007 self
.second
= self
.type(args
[1])
1009 raise TypeError, "Too many arguments specified"
1012 raise TypeError, "too many keywords: %s" % kwargs
.keys()
1015 return '%s:%s' % (self
.first
, self
.second
)
1017 # Metaclass for bounds-checked integer parameters. See CheckedInt.
1018 class CheckedIntType(type):
1019 def __init__(cls
, name
, bases
, dict):
1020 super(CheckedIntType
, cls
).__init
__(name
, bases
, dict)
1022 # CheckedInt is an abstract base class, so we actually don't
1023 # want to do any processing on it... the rest of this code is
1024 # just for classes that derive from CheckedInt.
1025 if name
== 'CheckedInt':
1028 if not (hasattr(cls
, 'min') and hasattr(cls
, 'max')):
1029 if not (hasattr(cls
, 'size') and hasattr(cls
, 'unsigned')):
1030 panic("CheckedInt subclass %s must define either\n" \
1031 " 'min' and 'max' or 'size' and 'unsigned'\n" \
1035 cls
.max = 2 ** cls
.size
- 1
1037 cls
.min = -(2 ** (cls
.size
- 1))
1038 cls
.max = (2 ** (cls
.size
- 1)) - 1
1040 # Abstract superclass for bounds-checked integer parameters. This
1041 # class is subclassed to generate parameter classes with specific
1042 # bounds. Initialization of the min and max bounds is done in the
1043 # metaclass CheckedIntType.__init__.
1044 class CheckedInt(NumericParamValue
):
1045 __metaclass__
= CheckedIntType
1048 if not self
.min <= self
.value
<= self
.max:
1049 raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
1050 (self
.min, self
.value
, self
.max)
1052 def __init__(self
, value
):
1053 if isinstance(value
, str):
1054 self
.value
= toInteger(value
)
1055 elif isinstance(value
, (int, long, float)):
1056 self
.value
= long(value
)
1059 class Int(CheckedInt
): size
= 32; unsigned
= False
1060 class Unsigned(CheckedInt
): size
= 32; unsigned
= True
1062 class Int8(CheckedInt
): size
= 8; unsigned
= False
1063 class UInt8(CheckedInt
): size
= 8; unsigned
= True
1064 class Int16(CheckedInt
): size
= 16; unsigned
= False
1065 class UInt16(CheckedInt
): size
= 16; unsigned
= True
1066 class Int32(CheckedInt
): size
= 32; unsigned
= False
1067 class UInt32(CheckedInt
): size
= 32; unsigned
= True
1068 class Int64(CheckedInt
): size
= 64; unsigned
= False
1069 class UInt64(CheckedInt
): size
= 64; unsigned
= True
1071 class Counter(CheckedInt
): size
= 64; unsigned
= True
1072 class Tick(CheckedInt
): size
= 64; unsigned
= True
1073 class TcpPort(CheckedInt
): size
= 16; unsigned
= True
1074 class UdpPort(CheckedInt
): size
= 16; unsigned
= True
1076 class Percent(CheckedInt
): min = 0; max = 100
1078 class Float(ParamValue
, float):
1081 class MemorySize(CheckedInt
):
1084 def __init__(self
, value
):
1085 if isinstance(value
, MemorySize
):
1086 self
.value
= value
.value
1088 self
.value
= toMemorySize(value
)
1091 class MemorySize32(CheckedInt
):
1094 def __init__(self
, value
):
1095 if isinstance(value
, MemorySize
):
1096 self
.value
= value
.value
1098 self
.value
= toMemorySize(value
)
1101 class Addr(CheckedInt
):
1104 def __init__(self
, value
):
1105 if isinstance(value
, Addr
):
1106 self
.value
= value
.value
1109 self
.value
= toMemorySize(value
)
1111 self
.value
= long(value
)
1114 class AddrRange(Range
):
1117 # String-valued parameter. Just mixin the ParamValue class
1118 # with the built-in str class.
1119 class String(ParamValue
,str):
1122 # Boolean parameter type. Python doesn't let you subclass bool, since
1123 # it doesn't want to let you create multiple instances of True and
1124 # False. Thus this is a little more complicated than String.
1125 class Bool(ParamValue
):
1126 def __init__(self
, value
):
1128 self
.value
= toBool(value
)
1130 self
.value
= bool(value
)
1133 return str(self
.value
)
1140 def IncEthernetAddr(addr
, val
= 1):
1141 bytes
= map(lambda x
: int(x
, 16), addr
.split(':'))
1143 for i
in (5, 4, 3, 2, 1):
1144 val
,rem
= divmod(bytes
[i
], 256)
1149 assert(bytes
[0] <= 255)
1150 return ':'.join(map(lambda x
: '%02x' % x
, bytes
))
1152 class NextEthernetAddr(object):
1153 addr
= "00:90:00:00:00:01"
1155 def __init__(self
, inc
= 1):
1156 self
.value
= NextEthernetAddr
.addr
1157 NextEthernetAddr
.addr
= IncEthernetAddr(NextEthernetAddr
.addr
, inc
)
1159 class EthernetAddr(ParamValue
):
1160 def __init__(self
, value
):
1161 if value
== NextEthernetAddr
:
1165 if not isinstance(value
, str):
1166 raise TypeError, "expected an ethernet address and didn't get one"
1168 bytes
= value
.split(':')
1170 raise TypeError, 'invalid ethernet address %s' % value
1173 if not 0 <= int(byte
) <= 256:
1174 raise TypeError, 'invalid ethernet address %s' % value
1178 def unproxy(self
, base
):
1179 if self
.value
== NextEthernetAddr
:
1180 self
.addr
= self
.value().value
1184 if self
.value
== NextEthernetAddr
:
1185 if hasattr(self
, 'addr'):
1188 return "NextEthernetAddr (unresolved)"
1192 # Special class for NULL pointers. Note the special check in
1193 # make_param_value() above that lets these be assigned where a
1194 # SimObject is required.
1195 # only one copy of a particular node
1196 class NullSimObject(object):
1197 __metaclass__
= Singleton
1202 def _instantiate(self
, parent
= None, path
= ''):
1208 def unproxy(self
, base
):
1211 def set_path(self
, parent
, name
):
1216 # The only instance you'll ever need...
1217 Null
= NULL
= NullSimObject()
1219 # Enumerated types are a little more complex. The user specifies the
1220 # type as Enum(foo) where foo is either a list or dictionary of
1221 # alternatives (typically strings, but not necessarily so). (In the
1222 # long run, the integer value of the parameter will be the list index
1223 # or the corresponding dictionary value. For now, since we only check
1224 # that the alternative is valid and then spit it into a .ini file,
1225 # there's not much point in using the dictionary.)
1227 # What Enum() must do is generate a new type encapsulating the
1228 # provided list/dictionary so that specific values of the parameter
1229 # can be instances of that type. We define two hidden internal
1230 # classes (_ListEnum and _DictEnum) to serve as base classes, then
1231 # derive the new type from the appropriate base class on the fly.
1234 # Metaclass for Enum types
1235 class MetaEnum(type):
1236 def __init__(cls
, name
, bases
, init_dict
):
1237 if init_dict
.has_key('map'):
1238 if not isinstance(cls
.map, dict):
1239 raise TypeError, "Enum-derived class attribute 'map' " \
1240 "must be of type dict"
1241 # build list of value strings from map
1242 cls
.vals
= cls
.map.keys()
1244 elif init_dict
.has_key('vals'):
1245 if not isinstance(cls
.vals
, list):
1246 raise TypeError, "Enum-derived class attribute 'vals' " \
1247 "must be of type list"
1248 # build string->value map from vals sequence
1250 for idx
,val
in enumerate(cls
.vals
):
1253 raise TypeError, "Enum-derived class must define "\
1254 "attribute 'map' or 'vals'"
1256 super(MetaEnum
, cls
).__init
__(name
, bases
, init_dict
)
1258 def cpp_declare(cls
):
1259 s
= 'enum %s {\n ' % cls
.__name
__
1260 s
+= ',\n '.join(['%s = %d' % (v
,cls
.map[v
]) for v
in cls
.vals
])
1264 # Base class for enum types.
1265 class Enum(ParamValue
):
1266 __metaclass__
= MetaEnum
1269 def __init__(self
, value
):
1270 if value
not in self
.map:
1271 raise TypeError, "Enum param got bad value '%s' (not in %s)" \
1272 % (value
, self
.vals
)
1278 ticks_per_sec
= None
1280 # how big does a rounding error need to be before we warn about it?
1281 frequency_tolerance
= 0.001 # 0.1%
1283 # convert a floting-point # of ticks to integer, and warn if rounding
1284 # discards too much precision
1285 def tick_check(float_ticks
):
1286 if float_ticks
== 0:
1288 int_ticks
= int(round(float_ticks
))
1289 err
= (float_ticks
- int_ticks
) / float_ticks
1290 if err
> frequency_tolerance
:
1291 print >> sys
.stderr
, "Warning: rounding error > tolerance"
1292 print >> sys
.stderr
, " %f rounded to %d" % (float_ticks
, int_ticks
)
1296 def getLatency(value
):
1297 if isinstance(value
, Latency
) or isinstance(value
, Clock
):
1299 elif isinstance(value
, Frequency
) or isinstance(value
, RootClock
):
1300 return 1 / value
.value
1301 elif isinstance(value
, str):
1303 return toLatency(value
)
1306 return 1 / toFrequency(value
)
1309 raise ValueError, "Invalid Frequency/Latency value '%s'" % value
1312 class Latency(NumericParamValue
):
1313 def __init__(self
, value
):
1314 self
.value
= getLatency(value
)
1316 def __getattr__(self
, attr
):
1317 if attr
in ('latency', 'period'):
1319 if attr
== 'frequency':
1320 return Frequency(self
)
1321 raise AttributeError, "Latency object has no attribute '%s'" % attr
1323 # convert latency to ticks
1325 return str(tick_check(self
.value
* ticks_per_sec
))
1327 class Frequency(NumericParamValue
):
1328 def __init__(self
, value
):
1329 self
.value
= 1 / getLatency(value
)
1331 def __getattr__(self
, attr
):
1332 if attr
== 'frequency':
1334 if attr
in ('latency', 'period'):
1335 return Latency(self
)
1336 raise AttributeError, "Frequency object has no attribute '%s'" % attr
1338 # convert frequency to ticks per period
1340 return self
.period
.ini_str()
1342 # Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz).
1343 # We can't inherit from Frequency because we don't want it to be directly
1344 # assignable to a regular Frequency parameter.
1345 class RootClock(ParamValue
):
1346 def __init__(self
, value
):
1347 self
.value
= 1 / getLatency(value
)
1349 def __getattr__(self
, attr
):
1350 if attr
== 'frequency':
1351 return Frequency(self
)
1352 if attr
in ('latency', 'period'):
1353 return Latency(self
)
1354 raise AttributeError, "Frequency object has no attribute '%s'" % attr
1357 return str(tick_check(self
.value
))
1359 # A generic frequency and/or Latency value. Value is stored as a latency,
1360 # but to avoid ambiguity this object does not support numeric ops (* or /).
1361 # An explicit conversion to a Latency or Frequency must be made first.
1362 class Clock(ParamValue
):
1363 def __init__(self
, value
):
1364 self
.value
= getLatency(value
)
1366 def __getattr__(self
, attr
):
1367 if attr
== 'frequency':
1368 return Frequency(self
)
1369 if attr
in ('latency', 'period'):
1370 return Latency(self
)
1371 raise AttributeError, "Frequency object has no attribute '%s'" % attr
1374 return self
.period
.ini_str()
1376 class NetworkBandwidth(float,ParamValue
):
1377 def __new__(cls
, value
):
1378 val
= toNetworkBandwidth(value
) / 8.0
1379 return super(cls
, NetworkBandwidth
).__new
__(cls
, val
)
1382 return str(self
.val
)
1385 return '%f' % (ticks_per_sec
/ float(self
))
1387 class MemoryBandwidth(float,ParamValue
):
1388 def __new__(self
, value
):
1389 val
= toMemoryBandwidth(value
)
1390 return super(cls
, MemoryBandwidth
).__new
__(cls
, val
)
1393 return str(self
.val
)
1396 return '%f' % (ticks_per_sec
/ float(self
))
1399 # "Constants"... handy aliases for various values.
1402 # Some memory range specifications use this as a default upper bound.
1405 AllMemory
= AddrRange(0, MaxAddr
)
1408 #####################################################################
1412 # Ports are used to interconnect objects in the memory system.
1414 #####################################################################
1416 # Port reference: encapsulates a reference to a particular port on a
1417 # particular SimObject.
1418 class PortRef(object):
1419 def __init__(self
, simobj
, name
, isVec
):
1420 assert(isSimObject(simobj
))
1421 self
.simobj
= simobj
1424 self
.isVec
= isVec
# is this a vector port?
1425 self
.peer
= None # not associated with another port yet
1426 self
.ccConnected
= False # C++ port connection done?
1428 # Set peer port reference. Called via __setattr__ as a result of
1429 # a port assignment, e.g., "obj1.port1 = obj2.port2".
1430 def setPeer(self
, other
):
1432 curMap
= self
.simobj
._port
_map
.get(self
.name
, [])
1433 self
.index
= len(curMap
)
1434 curMap
.append(other
)
1436 curMap
= self
.simobj
._port
_map
.get(self
.name
)
1437 if curMap
and not self
.isVec
:
1438 print "warning: overwriting port", self
.simobj
, self
.name
1440 self
.simobj
._port
_map
[self
.name
] = curMap
1443 def clone(self
, memo
):
1444 newRef
= copy
.copy(self
)
1445 assert(isSimObject(newRef
.simobj
))
1446 newRef
.simobj
= newRef
.simobj(_memo
=memo
)
1447 # Tricky: if I'm the *second* PortRef in the pair to be
1448 # cloned, then my peer is still in the middle of its clone
1449 # method, and thus hasn't returned to its owner's
1450 # SimObject.__init__ to get installed in _port_map. As a
1451 # result I have no way of finding the *new* peer object. So I
1452 # mark myself as "waiting" for my peer, and I let the *first*
1453 # PortRef clone call set up both peer pointers after I return.
1454 newPeer
= newRef
.simobj
._port
_map
.get(self
.name
)
1457 assert(self
.index
!= -1)
1458 newPeer
= newPeer
[self
.index
]
1459 # other guy is all set up except for his peer pointer
1460 assert(newPeer
.peer
== -1) # peer must be waiting for handshake
1461 newPeer
.peer
= newRef
1462 newRef
.peer
= newPeer
1464 # other guy is in clone; just wait for him to do the work
1465 newRef
.peer
= -1 # mark as waiting for handshake
1468 # Call C++ to create corresponding port connection between C++ objects
1469 def ccConnect(self
):
1470 if self
.ccConnected
: # already done this
1473 cc_main
.connectPorts(self
.simobj
.getCCObject(), self
.name
, self
.index
,
1474 peer
.simobj
.getCCObject(), peer
.name
, peer
.index
)
1475 self
.ccConnected
= True
1476 peer
.ccConnected
= True
1478 # Port description object. Like a ParamDesc object, this represents a
1479 # logical port in the SimObject class, not a particular port on a
1480 # SimObject instance. The latter are represented by PortRef objects.
1482 def __init__(self
, desc
):
1486 # Generate a PortRef for this port on the given SimObject with the
1488 def makeRef(self
, simobj
, name
):
1489 return PortRef(simobj
, name
, self
.isVec
)
1491 # Connect an instance of this port (on the given SimObject with
1492 # the given name) with the port described by the supplied PortRef
1493 def connect(self
, simobj
, name
, ref
):
1494 if not isinstance(ref
, PortRef
):
1496 "assigning non-port reference port '%s'" % name
1497 myRef
= self
.makeRef(simobj
, name
)
1501 # VectorPort description object. Like Port, but represents a vector
1502 # of connections (e.g., as on a Bus).
1503 class VectorPort(Port
):
1504 def __init__(self
, desc
):
1505 Port
.__init
__(self
, desc
)
1508 #####################################################################
1510 # __all__ defines the list of symbols that get exported when
1511 # 'from config import *' is invoked. Try to keep this reasonably
1512 # short to avoid polluting other namespaces.
1513 __all__
= ['SimObject', 'ParamContext', 'Param', 'VectorParam',
1515 'Enum', 'Bool', 'String', 'Float',
1516 'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1517 'Int32', 'UInt32', 'Int64', 'UInt64',
1518 'Counter', 'Addr', 'Tick', 'Percent',
1519 'TcpPort', 'UdpPort', 'EthernetAddr',
1520 'MemorySize', 'MemorySize32',
1521 'Latency', 'Frequency', 'RootClock', 'Clock',
1522 'NetworkBandwidth', 'MemoryBandwidth',
1523 'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory',
1526 'Port', 'VectorPort']