From 1233dbb9981ad27abeb22f9dcba20bd77abab041 Mon Sep 17 00:00:00 2001 From: Steve Reinhardt Date: Mon, 4 Sep 2006 10:52:26 -0700 Subject: [PATCH] Split config.py into multiple files. Some tweaking to deal with mutually recursive imports. --HG-- rename : src/python/m5/config.py => src/python/m5/SimObject.py extra : convert_revision : 166f7bfabfd20100e93d26a89382469465859988 --- src/python/m5/SimObject.py | 737 ++++++++++++++++ src/python/m5/__init__.py | 24 +- src/python/m5/config.py | 1717 ------------------------------------ src/python/m5/params.py | 831 +++++++++++++++++ src/python/m5/proxy.py | 193 ++++ src/python/m5/util.py | 59 ++ 6 files changed, 1833 insertions(+), 1728 deletions(-) create mode 100644 src/python/m5/SimObject.py delete mode 100644 src/python/m5/config.py create mode 100644 src/python/m5/params.py create mode 100644 src/python/m5/proxy.py create mode 100644 src/python/m5/util.py diff --git a/src/python/m5/SimObject.py b/src/python/m5/SimObject.py new file mode 100644 index 000000000..33fa51665 --- /dev/null +++ b/src/python/m5/SimObject.py @@ -0,0 +1,737 @@ +# Copyright (c) 2004-2006 The Regents of The University of Michigan +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Steve Reinhardt +# Nathan Binkert + +import sys, types + +import m5 +from m5 import panic, cc_main +from convert import * +from multidict import multidict + +noDot = False +try: + import pydot +except: + noDot = True + +##################################################################### +# +# M5 Python Configuration Utility +# +# The basic idea is to write simple Python programs that build Python +# objects corresponding to M5 SimObjects for the desired simulation +# configuration. For now, the Python emits a .ini file that can be +# parsed by M5. In the future, some tighter integration between M5 +# and the Python interpreter may allow bypassing the .ini file. +# +# Each SimObject class in M5 is represented by a Python class with the +# same name. The Python inheritance tree mirrors the M5 C++ tree +# (e.g., SimpleCPU derives from BaseCPU in both cases, and all +# SimObjects inherit from a single SimObject base class). To specify +# an instance of an M5 SimObject in a configuration, the user simply +# instantiates the corresponding Python object. The parameters for +# that SimObject are given by assigning to attributes of the Python +# object, either using keyword assignment in the constructor or in +# separate assignment statements. For example: +# +# cache = BaseCache(size='64KB') +# cache.hit_latency = 3 +# cache.assoc = 8 +# +# The magic lies in the mapping of the Python attributes for SimObject +# classes to the actual SimObject parameter specifications. This +# allows parameter validity checking in the Python code. Continuing +# the example above, the statements "cache.blurfl=3" or +# "cache.assoc='hello'" would both result in runtime errors in Python, +# since the BaseCache object has no 'blurfl' parameter and the 'assoc' +# parameter requires an integer, respectively. This magic is done +# primarily by overriding the special __setattr__ method that controls +# assignment to object attributes. +# +# Once a set of Python objects have been instantiated in a hierarchy, +# calling 'instantiate(obj)' (where obj is the root of the hierarchy) +# will generate a .ini file. +# +##################################################################### + +# dict to look up SimObjects based on path +instanceDict = {} + +# The metaclass for SimObject. This class controls how new classes +# that derive from SimObject are instantiated, and provides inherited +# class behavior (just like a class controls how instances of that +# class are instantiated, and provides inherited instance behavior). +class MetaSimObject(type): + # Attributes that can be set only at initialization time + init_keywords = { 'abstract' : types.BooleanType, + 'type' : types.StringType } + # Attributes that can be set any time + keywords = { 'check' : types.FunctionType, + 'cxx_type' : types.StringType, + 'cxx_predecls' : types.ListType, + 'swig_predecls' : types.ListType } + + # __new__ is called before __init__, and is where the statements + # in the body of the class definition get loaded into the class's + # __dict__. We intercept this to filter out parameter & port assignments + # and only allow "private" attributes to be passed to the base + # __new__ (starting with underscore). + def __new__(mcls, name, bases, dict): + # Copy "private" attributes, functions, and classes to the + # official dict. Everything else goes in _init_dict to be + # filtered in __init__. + cls_dict = {} + value_dict = {} + for key,val in dict.items(): + if key.startswith('_') or isinstance(val, (types.FunctionType, + types.TypeType)): + cls_dict[key] = val + else: + # must be a param/port setting + value_dict[key] = val + cls_dict['_value_dict'] = value_dict + return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict) + + # subclass initialization + def __init__(cls, name, bases, dict): + # calls type.__init__()... I think that's a no-op, but leave + # it here just in case it's not. + super(MetaSimObject, cls).__init__(name, bases, dict) + + # initialize required attributes + + # class-only attributes + cls._params = multidict() # param descriptions + cls._ports = multidict() # port descriptions + + # class or instance attributes + cls._values = multidict() # param values + cls._port_map = multidict() # port bindings + cls._instantiated = False # really instantiated, cloned, or subclassed + + # We don't support multiple inheritance. If you want to, you + # must fix multidict to deal with it properly. + if len(bases) > 1: + raise TypeError, "SimObjects do not support multiple inheritance" + + base = bases[0] + + # Set up general inheritance via multidicts. A subclass will + # inherit all its settings from the base class. The only time + # the following is not true is when we define the SimObject + # class itself (in which case the multidicts have no parent). + if isinstance(base, MetaSimObject): + cls._params.parent = base._params + cls._ports.parent = base._ports + cls._values.parent = base._values + cls._port_map.parent = base._port_map + # mark base as having been subclassed + base._instantiated = True + + # Now process the _value_dict items. They could be defining + # new (or overriding existing) parameters or ports, setting + # class keywords (e.g., 'abstract'), or setting parameter + # values or port bindings. The first 3 can only be set when + # the class is defined, so we handle them here. The others + # can be set later too, so just emulate that by calling + # setattr(). + for key,val in cls._value_dict.items(): + # param descriptions + if isinstance(val, ParamDesc): + cls._new_param(key, val) + + # port objects + elif isinstance(val, Port): + cls._ports[key] = val + + # init-time-only keywords + elif cls.init_keywords.has_key(key): + cls._set_keyword(key, val, cls.init_keywords[key]) + + # default: use normal path (ends up in __setattr__) + else: + setattr(cls, key, val) + + cls.cxx_type = cls.type + '*' + # A forward class declaration is sufficient since we are just + # declaring a pointer. + cls.cxx_predecls = ['class %s;' % cls.type] + cls.swig_predecls = cls.cxx_predecls + + def _set_keyword(cls, keyword, val, kwtype): + if not isinstance(val, kwtype): + raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \ + (keyword, type(val), kwtype) + if isinstance(val, types.FunctionType): + val = classmethod(val) + type.__setattr__(cls, keyword, val) + + def _new_param(cls, name, pdesc): + # each param desc should be uniquely assigned to one variable + assert(not hasattr(pdesc, 'name')) + pdesc.name = name + cls._params[name] = pdesc + if hasattr(pdesc, 'default'): + setattr(cls, name, pdesc.default) + + # Set attribute (called on foo.attr = value when foo is an + # instance of class cls). + def __setattr__(cls, attr, value): + # normal processing for private attributes + if attr.startswith('_'): + type.__setattr__(cls, attr, value) + return + + if cls.keywords.has_key(attr): + cls._set_keyword(attr, value, cls.keywords[attr]) + return + + if cls._ports.has_key(attr): + self._ports[attr].connect(self, attr, value) + return + + if isSimObjectOrSequence(value) and cls._instantiated: + raise RuntimeError, \ + "cannot set SimObject parameter '%s' after\n" \ + " class %s has been instantiated or subclassed" \ + % (attr, cls.__name__) + + # check for param + param = cls._params.get(attr, None) + if param: + try: + cls._values[attr] = param.convert(value) + except Exception, e: + msg = "%s\nError setting param %s.%s to %s\n" % \ + (e, cls.__name__, attr, value) + e.args = (msg, ) + raise + elif isSimObjectOrSequence(value): + # if RHS is a SimObject, it's an implicit child assignment + cls._values[attr] = value + else: + raise AttributeError, \ + "Class %s has no parameter \'%s\'" % (cls.__name__, attr) + + def __getattr__(cls, attr): + if cls._values.has_key(attr): + return cls._values[attr] + + raise AttributeError, \ + "object '%s' has no attribute '%s'" % (cls.__name__, attr) + + def __str__(cls): + return cls.__name__ + + def cxx_decl(cls): + code = "#ifndef __PARAMS__%s\n#define __PARAMS__%s\n\n" % (cls, cls) + + if str(cls) != 'SimObject': + base = cls.__bases__[0].type + else: + base = None + + # The 'dict' attribute restricts us to the params declared in + # the object itself, not including inherited params (which + # will also be inherited from the base class's param struct + # here). + params = cls._params.dict.values() + try: + ptypes = [p.ptype for p in params] + except: + print cls, p, p.ptype_str + print params + raise + + # get a list of lists of predeclaration lines + predecls = [p.cxx_predecls() for p in params] + # flatten + predecls = reduce(lambda x,y:x+y, predecls, []) + # remove redundant lines + predecls2 = [] + for pd in predecls: + if pd not in predecls2: + predecls2.append(pd) + predecls2.sort() + code += "\n".join(predecls2) + code += "\n\n"; + + if base: + code += '#include "params/%s.hh"\n\n' % base + + # Generate declarations for locally defined enumerations. + enum_ptypes = [t for t in ptypes if issubclass(t, Enum)] + if enum_ptypes: + code += "\n".join([t.cxx_decl() for t in enum_ptypes]) + code += "\n\n" + + # now generate the actual param struct + code += "struct %sParams" % cls + if base: + code += " : public %sParams" % base + code += " {\n" + decls = [p.cxx_decl() for p in params] + decls.sort() + code += "".join([" %s\n" % d for d in decls]) + code += "};\n" + + # close #ifndef __PARAMS__* guard + code += "\n#endif\n" + return code + + def swig_decl(cls): + + code = '%%module %sParams\n' % cls + + if str(cls) != 'SimObject': + base = cls.__bases__[0].type + else: + base = None + + # The 'dict' attribute restricts us to the params declared in + # the object itself, not including inherited params (which + # will also be inherited from the base class's param struct + # here). + params = cls._params.dict.values() + ptypes = [p.ptype for p in params] + + # get a list of lists of predeclaration lines + predecls = [p.swig_predecls() for p in params] + # flatten + predecls = reduce(lambda x,y:x+y, predecls, []) + # remove redundant lines + predecls2 = [] + for pd in predecls: + if pd not in predecls2: + predecls2.append(pd) + predecls2.sort() + code += "\n".join(predecls2) + code += "\n\n"; + + if base: + code += '%%import "python/m5/swig/%sParams.i"\n\n' % base + + code += '%{\n' + code += '#include "params/%s.hh"\n' % cls + code += '%}\n\n' + code += '%%include "params/%s.hh"\n\n' % cls + + return code + +# The SimObject class is the root of the special hierarchy. Most of +# the code in this class deals with the configuration hierarchy itself +# (parent/child node relationships). +class SimObject(object): + # Specify metaclass. Any class inheriting from SimObject will + # get this metaclass. + __metaclass__ = MetaSimObject + type = 'SimObject' + + name = Param.String("Object name") + + # Initialize new instance. For objects with SimObject-valued + # children, we need to recursively clone the classes represented + # by those param values as well in a consistent "deep copy"-style + # fashion. That is, we want to make sure that each instance is + # cloned only once, and that if there are multiple references to + # the same original object, we end up with the corresponding + # cloned references all pointing to the same cloned instance. + def __init__(self, **kwargs): + ancestor = kwargs.get('_ancestor') + memo_dict = kwargs.get('_memo') + if memo_dict is None: + # prepare to memoize any recursively instantiated objects + memo_dict = {} + elif ancestor: + # memoize me now to avoid problems with recursive calls + memo_dict[ancestor] = self + + if not ancestor: + ancestor = self.__class__ + ancestor._instantiated = True + + # initialize required attributes + self._parent = None + self._children = {} + self._ccObject = None # pointer to C++ object + self._instantiated = False # really "cloned" + + # Inherit parameter values from class using multidict so + # individual value settings can be overridden. + self._values = multidict(ancestor._values) + # clone SimObject-valued parameters + for key,val in ancestor._values.iteritems(): + if isSimObject(val): + setattr(self, key, val(_memo=memo_dict)) + elif isSimObjectSequence(val) and len(val): + setattr(self, key, [ v(_memo=memo_dict) for v in val ]) + # clone port references. no need to use a multidict here + # since we will be creating new references for all ports. + self._port_map = {} + for key,val in ancestor._port_map.iteritems(): + self._port_map[key] = applyOrMap(val, 'clone', memo_dict) + # apply attribute assignments from keyword args, if any + for key,val in kwargs.iteritems(): + setattr(self, key, val) + + # "Clone" the current instance by creating another instance of + # this instance's class, but that inherits its parameter values + # and port mappings from the current instance. If we're in a + # "deep copy" recursive clone, check the _memo dict to see if + # we've already cloned this instance. + def __call__(self, **kwargs): + memo_dict = kwargs.get('_memo') + if memo_dict is None: + # no memo_dict: must be top-level clone operation. + # this is only allowed at the root of a hierarchy + if self._parent: + raise RuntimeError, "attempt to clone object %s " \ + "not at the root of a tree (parent = %s)" \ + % (self, self._parent) + # create a new dict and use that. + memo_dict = {} + kwargs['_memo'] = memo_dict + elif memo_dict.has_key(self): + # clone already done & memoized + return memo_dict[self] + return self.__class__(_ancestor = self, **kwargs) + + def __getattr__(self, attr): + if self._ports.has_key(attr): + # return reference that can be assigned to another port + # via __setattr__ + return self._ports[attr].makeRef(self, attr) + + if self._values.has_key(attr): + return self._values[attr] + + raise AttributeError, "object '%s' has no attribute '%s'" \ + % (self.__class__.__name__, attr) + + # Set attribute (called on foo.attr = value when foo is an + # instance of class cls). + def __setattr__(self, attr, value): + # normal processing for private attributes + if attr.startswith('_'): + object.__setattr__(self, attr, value) + return + + if self._ports.has_key(attr): + # set up port connection + self._ports[attr].connect(self, attr, value) + return + + if isSimObjectOrSequence(value) and self._instantiated: + raise RuntimeError, \ + "cannot set SimObject parameter '%s' after\n" \ + " instance been cloned %s" % (attr, `self`) + + # must be SimObject param + param = self._params.get(attr, None) + if param: + try: + value = param.convert(value) + except Exception, e: + msg = "%s\nError setting param %s.%s to %s\n" % \ + (e, self.__class__.__name__, attr, value) + e.args = (msg, ) + raise + elif isSimObjectOrSequence(value): + pass + else: + raise AttributeError, "Class %s has no parameter %s" \ + % (self.__class__.__name__, attr) + + # clear out old child with this name, if any + self.clear_child(attr) + + if isSimObject(value): + value.set_path(self, attr) + elif isSimObjectSequence(value): + value = SimObjVector(value) + [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)] + + self._values[attr] = value + + # this hack allows tacking a '[0]' onto parameters that may or may + # not be vectors, and always getting the first element (e.g. cpus) + def __getitem__(self, key): + if key == 0: + return self + raise TypeError, "Non-zero index '%s' to SimObject" % key + + # clear out children with given name, even if it's a vector + def clear_child(self, name): + if not self._children.has_key(name): + return + child = self._children[name] + if isinstance(child, SimObjVector): + for i in xrange(len(child)): + del self._children["s%d" % (name, i)] + del self._children[name] + + def add_child(self, name, value): + self._children[name] = value + + def set_path(self, parent, name): + if not self._parent: + self._parent = parent + self._name = name + parent.add_child(name, self) + + def path(self): + if not self._parent: + return 'root' + ppath = self._parent.path() + if ppath == 'root': + return self._name + return ppath + "." + self._name + + def __str__(self): + return self.path() + + def ini_str(self): + return self.path() + + def find_any(self, ptype): + if isinstance(self, ptype): + return self, True + + found_obj = None + for child in self._children.itervalues(): + if isinstance(child, ptype): + if found_obj != None and child != found_obj: + raise AttributeError, \ + 'parent.any matched more than one: %s %s' % \ + (found_obj.path, child.path) + found_obj = child + # search param space + for pname,pdesc in self._params.iteritems(): + if issubclass(pdesc.ptype, ptype): + match_obj = self._values[pname] + if found_obj != None and found_obj != match_obj: + raise AttributeError, \ + 'parent.any matched more than one: %s' % obj.path + found_obj = match_obj + return found_obj, found_obj != None + + def unproxy(self, base): + return self + + def print_ini(self): + print '[' + self.path() + ']' # .ini section header + + instanceDict[self.path()] = self + + if hasattr(self, 'type') and not isinstance(self, ParamContext): + print 'type=%s' % self.type + + child_names = self._children.keys() + child_names.sort() + np_child_names = [c for c in child_names \ + if not isinstance(self._children[c], ParamContext)] + if len(np_child_names): + print 'children=%s' % ' '.join(np_child_names) + + param_names = self._params.keys() + param_names.sort() + for param in param_names: + value = self._values.get(param, None) + if value != None: + if isproxy(value): + try: + value = value.unproxy(self) + except: + print >> sys.stderr, \ + "Error in unproxying param '%s' of %s" % \ + (param, self.path()) + raise + setattr(self, param, value) + print '%s=%s' % (param, self._values[param].ini_str()) + + print # blank line between objects + + for child in child_names: + self._children[child].print_ini() + + # Call C++ to create C++ object corresponding to this object and + # (recursively) all its children + def createCCObject(self): + self.getCCObject() # force creation + for child in self._children.itervalues(): + child.createCCObject() + + # Get C++ object corresponding to this object, calling C++ if + # necessary to construct it. Does *not* recursively create + # children. + def getCCObject(self): + if not self._ccObject: + self._ccObject = -1 # flag to catch cycles in recursion + self._ccObject = cc_main.createSimObject(self.path()) + elif self._ccObject == -1: + raise RuntimeError, "%s: recursive call to getCCObject()" \ + % self.path() + return self._ccObject + + # Create C++ port connections corresponding to the connections in + # _port_map (& recursively for all children) + def connectPorts(self): + for portRef in self._port_map.itervalues(): + applyOrMap(portRef, 'ccConnect') + for child in self._children.itervalues(): + child.connectPorts() + + def startDrain(self, drain_event, recursive): + count = 0 + # ParamContexts don't serialize + if isinstance(self, SimObject) and not isinstance(self, ParamContext): + count += self._ccObject.drain(drain_event) + if recursive: + for child in self._children.itervalues(): + count += child.startDrain(drain_event, True) + return count + + def resume(self): + if isinstance(self, SimObject) and not isinstance(self, ParamContext): + self._ccObject.resume() + for child in self._children.itervalues(): + child.resume() + + def changeTiming(self, mode): + if isinstance(self, System): + self._ccObject.setMemoryMode(mode) + for child in self._children.itervalues(): + child.changeTiming(mode) + + def takeOverFrom(self, old_cpu): + cpu_ptr = cc_main.convertToBaseCPUPtr(old_cpu._ccObject) + self._ccObject.takeOverFrom(cpu_ptr) + + # generate output file for 'dot' to display as a pretty graph. + # this code is currently broken. + def outputDot(self, dot): + label = "{%s|" % self.path + if isSimObject(self.realtype): + label += '%s|' % self.type + + if self.children: + # instantiate children in same order they were added for + # backward compatibility (else we can end up with cpu1 + # before cpu0). + for c in self.children: + dot.add_edge(pydot.Edge(self.path,c.path, style="bold")) + + simobjs = [] + for param in self.params: + try: + if param.value is None: + raise AttributeError, 'Parameter with no value' + + value = param.value + string = param.string(value) + except Exception, e: + msg = 'exception in %s:%s\n%s' % (self.name, param.name, e) + e.args = (msg, ) + raise + + if isSimObject(param.ptype) and string != "Null": + simobjs.append(string) + else: + label += '%s = %s\\n' % (param.name, string) + + for so in simobjs: + label += "|<%s> %s" % (so, so) + dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, + tailport="w")) + label += '}' + dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label)) + + # recursively dump out children + for c in self.children: + c.outputDot(dot) + +class ParamContext(SimObject): + pass + +# Special class for NULL pointers. Note the special check in +# make_param_value() above that lets these be assigned where a +# SimObject is required. +# only one copy of a particular node +class NullSimObject(object): + __metaclass__ = Singleton + + def __call__(cls): + return cls + + def _instantiate(self, parent = None, path = ''): + pass + + def ini_str(self): + return 'Null' + + def unproxy(self, base): + return self + + def set_path(self, parent, name): + pass + def __str__(self): + return 'Null' + +# The only instance you'll ever need... +Null = NULL = NullSimObject() + +def isSimObject(value): + return isinstance(value, SimObject) + +def isNullPointer(value): + return isinstance(value, NullSimObject) + +def isSimObjectSequence(value): + if not isinstance(value, (list, tuple)) or len(value) == 0: + return False + + for val in value: + if not isNullPointer(val) and not isSimObject(val): + return False + + return True + +def isSimObjectOrSequence(value): + return isSimObject(value) or isSimObjectSequence(value) + +# Function to provide to C++ so it can look up instances based on paths +def resolveSimObject(name): + obj = instanceDict[name] + return obj.getCCObject() + +# __all__ defines the list of symbols that get exported when +# 'from config import *' is invoked. Try to keep this reasonably +# short to avoid polluting other namespaces. +__all__ = ['SimObject', 'ParamContext'] + diff --git a/src/python/m5/__init__.py b/src/python/m5/__init__.py index 950d605df..bd2efac09 100644 --- a/src/python/m5/__init__.py +++ b/src/python/m5/__init__.py @@ -71,17 +71,12 @@ build_env.update(defines.m5_build_env) env = smartdict.SmartDict() env.update(os.environ) -# Function to provide to C++ so it can look up instances based on paths -def resolveSimObject(name): - obj = config.instanceDict[name] - return obj.getCCObject() - from main import options, arguments, main # The final hook to generate .ini files. Called from the user script # once the config is built. def instantiate(root): - config.ticks_per_sec = float(root.clock.frequency) + params.ticks_per_sec = float(root.clock.frequency) # ugly temporary hack to get output to config.ini sys.stdout = file(os.path.join(options.outdir, 'config.ini'), 'w') root.print_ini() @@ -109,11 +104,6 @@ def curTick(): # register our C++ exit callback function with Python atexit.register(cc_main.doExitCleanup) -# This import allows user scripts to reference 'm5.objects.Foo' after -# just doing an 'import m5' (without an 'import m5.objects'). May not -# matter since most scripts will probably 'from m5.objects import *'. -import objects - # This loops until all objects have been fully drained. def doDrain(root): all_drained = drain(root) @@ -206,3 +196,15 @@ def switchCpus(cpuList): new_cpu.takeOverFrom(old_cpus[index]) new_cpu._ccObject.resume() index += 1 + +# Since we have so many mutual imports in this package, we should: +# 1. Put all intra-package imports at the *bottom* of the file, unless +# they're absolutely needed before that (for top-level statements +# or class attributes). Imports of "trivial" packages that don't +# import other packages (e.g., 'smartdict') can be at the top. +# 2. Never use 'from foo import *' on an intra-package import since +# you can get the wrong result if foo is only partially imported +# at the point you do that (i.e., because foo is in the middle of +# importing *you*). +import objects +import params diff --git a/src/python/m5/config.py b/src/python/m5/config.py deleted file mode 100644 index 38e7270a6..000000000 --- a/src/python/m5/config.py +++ /dev/null @@ -1,1717 +0,0 @@ -# Copyright (c) 2004-2006 The Regents of The University of Michigan -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer; -# redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution; -# neither the name of the copyright holders nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# Authors: Steve Reinhardt -# Nathan Binkert - -import os, re, sys, types, inspect, copy - -import m5 -from m5 import panic, cc_main -from convert import * -from multidict import multidict - -noDot = False -try: - import pydot -except: - noDot = True - -class Singleton(type): - def __call__(cls, *args, **kwargs): - if hasattr(cls, '_instance'): - return cls._instance - - cls._instance = super(Singleton, cls).__call__(*args, **kwargs) - return cls._instance - -##################################################################### -# -# M5 Python Configuration Utility -# -# The basic idea is to write simple Python programs that build Python -# objects corresponding to M5 SimObjects for the desired simulation -# configuration. For now, the Python emits a .ini file that can be -# parsed by M5. In the future, some tighter integration between M5 -# and the Python interpreter may allow bypassing the .ini file. -# -# Each SimObject class in M5 is represented by a Python class with the -# same name. The Python inheritance tree mirrors the M5 C++ tree -# (e.g., SimpleCPU derives from BaseCPU in both cases, and all -# SimObjects inherit from a single SimObject base class). To specify -# an instance of an M5 SimObject in a configuration, the user simply -# instantiates the corresponding Python object. The parameters for -# that SimObject are given by assigning to attributes of the Python -# object, either using keyword assignment in the constructor or in -# separate assignment statements. For example: -# -# cache = BaseCache(size='64KB') -# cache.hit_latency = 3 -# cache.assoc = 8 -# -# The magic lies in the mapping of the Python attributes for SimObject -# classes to the actual SimObject parameter specifications. This -# allows parameter validity checking in the Python code. Continuing -# the example above, the statements "cache.blurfl=3" or -# "cache.assoc='hello'" would both result in runtime errors in Python, -# since the BaseCache object has no 'blurfl' parameter and the 'assoc' -# parameter requires an integer, respectively. This magic is done -# primarily by overriding the special __setattr__ method that controls -# assignment to object attributes. -# -# Once a set of Python objects have been instantiated in a hierarchy, -# calling 'instantiate(obj)' (where obj is the root of the hierarchy) -# will generate a .ini file. -# -##################################################################### - -# dict to look up SimObjects based on path -instanceDict = {} - -############################# -# -# Utility methods -# -############################# - -def isSimObject(value): - return isinstance(value, SimObject) - -def isSimObjectSequence(value): - if not isinstance(value, (list, tuple)) or len(value) == 0: - return False - - for val in value: - if not isNullPointer(val) and not isSimObject(val): - return False - - return True - -def isSimObjectOrSequence(value): - return isSimObject(value) or isSimObjectSequence(value) - -def isNullPointer(value): - return isinstance(value, NullSimObject) - -# Apply method to object. -# applyMethod(obj, 'meth', ) is equivalent to obj.meth() -def applyMethod(obj, meth, *args, **kwargs): - return getattr(obj, meth)(*args, **kwargs) - -# If the first argument is an (non-sequence) object, apply the named -# method with the given arguments. If the first argument is a -# sequence, apply the method to each element of the sequence (a la -# 'map'). -def applyOrMap(objOrSeq, meth, *args, **kwargs): - if not isinstance(objOrSeq, (list, tuple)): - return applyMethod(objOrSeq, meth, *args, **kwargs) - else: - return [applyMethod(o, meth, *args, **kwargs) for o in objOrSeq] - - -# The metaclass for SimObject. This class controls how new classes -# that derive from SimObject are instantiated, and provides inherited -# class behavior (just like a class controls how instances of that -# class are instantiated, and provides inherited instance behavior). -class MetaSimObject(type): - # Attributes that can be set only at initialization time - init_keywords = { 'abstract' : types.BooleanType, - 'type' : types.StringType } - # Attributes that can be set any time - keywords = { 'check' : types.FunctionType, - 'cxx_type' : types.StringType, - 'cxx_predecls' : types.ListType, - 'swig_predecls' : types.ListType } - - # __new__ is called before __init__, and is where the statements - # in the body of the class definition get loaded into the class's - # __dict__. We intercept this to filter out parameter & port assignments - # and only allow "private" attributes to be passed to the base - # __new__ (starting with underscore). - def __new__(mcls, name, bases, dict): - # Copy "private" attributes, functions, and classes to the - # official dict. Everything else goes in _init_dict to be - # filtered in __init__. - cls_dict = {} - value_dict = {} - for key,val in dict.items(): - if key.startswith('_') or isinstance(val, (types.FunctionType, - types.TypeType)): - cls_dict[key] = val - else: - # must be a param/port setting - value_dict[key] = val - cls_dict['_value_dict'] = value_dict - return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict) - - # subclass initialization - def __init__(cls, name, bases, dict): - # calls type.__init__()... I think that's a no-op, but leave - # it here just in case it's not. - super(MetaSimObject, cls).__init__(name, bases, dict) - - # initialize required attributes - - # class-only attributes - cls._params = multidict() # param descriptions - cls._ports = multidict() # port descriptions - - # class or instance attributes - cls._values = multidict() # param values - cls._port_map = multidict() # port bindings - cls._instantiated = False # really instantiated, cloned, or subclassed - - # We don't support multiple inheritance. If you want to, you - # must fix multidict to deal with it properly. - if len(bases) > 1: - raise TypeError, "SimObjects do not support multiple inheritance" - - base = bases[0] - - # Set up general inheritance via multidicts. A subclass will - # inherit all its settings from the base class. The only time - # the following is not true is when we define the SimObject - # class itself (in which case the multidicts have no parent). - if isinstance(base, MetaSimObject): - cls._params.parent = base._params - cls._ports.parent = base._ports - cls._values.parent = base._values - cls._port_map.parent = base._port_map - # mark base as having been subclassed - base._instantiated = True - - # Now process the _value_dict items. They could be defining - # new (or overriding existing) parameters or ports, setting - # class keywords (e.g., 'abstract'), or setting parameter - # values or port bindings. The first 3 can only be set when - # the class is defined, so we handle them here. The others - # can be set later too, so just emulate that by calling - # setattr(). - for key,val in cls._value_dict.items(): - # param descriptions - if isinstance(val, ParamDesc): - cls._new_param(key, val) - - # port objects - elif isinstance(val, Port): - cls._ports[key] = val - - # init-time-only keywords - elif cls.init_keywords.has_key(key): - cls._set_keyword(key, val, cls.init_keywords[key]) - - # default: use normal path (ends up in __setattr__) - else: - setattr(cls, key, val) - - cls.cxx_type = cls.type + '*' - # A forward class declaration is sufficient since we are just - # declaring a pointer. - cls.cxx_predecls = ['class %s;' % cls.type] - cls.swig_predecls = cls.cxx_predecls - - def _set_keyword(cls, keyword, val, kwtype): - if not isinstance(val, kwtype): - raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \ - (keyword, type(val), kwtype) - if isinstance(val, types.FunctionType): - val = classmethod(val) - type.__setattr__(cls, keyword, val) - - def _new_param(cls, name, pdesc): - # each param desc should be uniquely assigned to one variable - assert(not hasattr(pdesc, 'name')) - pdesc.name = name - cls._params[name] = pdesc - if hasattr(pdesc, 'default'): - setattr(cls, name, pdesc.default) - - # Set attribute (called on foo.attr = value when foo is an - # instance of class cls). - def __setattr__(cls, attr, value): - # normal processing for private attributes - if attr.startswith('_'): - type.__setattr__(cls, attr, value) - return - - if cls.keywords.has_key(attr): - cls._set_keyword(attr, value, cls.keywords[attr]) - return - - if cls._ports.has_key(attr): - self._ports[attr].connect(self, attr, value) - return - - if isSimObjectOrSequence(value) and cls._instantiated: - raise RuntimeError, \ - "cannot set SimObject parameter '%s' after\n" \ - " class %s has been instantiated or subclassed" \ - % (attr, cls.__name__) - - # check for param - param = cls._params.get(attr, None) - if param: - try: - cls._values[attr] = param.convert(value) - except Exception, e: - msg = "%s\nError setting param %s.%s to %s\n" % \ - (e, cls.__name__, attr, value) - e.args = (msg, ) - raise - elif isSimObjectOrSequence(value): - # if RHS is a SimObject, it's an implicit child assignment - cls._values[attr] = value - else: - raise AttributeError, \ - "Class %s has no parameter \'%s\'" % (cls.__name__, attr) - - def __getattr__(cls, attr): - if cls._values.has_key(attr): - return cls._values[attr] - - raise AttributeError, \ - "object '%s' has no attribute '%s'" % (cls.__name__, attr) - - def __str__(cls): - return cls.__name__ - - def cxx_decl(cls): - code = "#ifndef __PARAMS__%s\n#define __PARAMS__%s\n\n" % (cls, cls) - - if str(cls) != 'SimObject': - base = cls.__bases__[0].type - else: - base = None - - # The 'dict' attribute restricts us to the params declared in - # the object itself, not including inherited params (which - # will also be inherited from the base class's param struct - # here). - params = cls._params.dict.values() - try: - ptypes = [p.ptype for p in params] - except: - print cls, p, p.ptype_str - print params - raise - - # get a list of lists of predeclaration lines - predecls = [p.cxx_predecls() for p in params] - # flatten - predecls = reduce(lambda x,y:x+y, predecls, []) - # remove redundant lines - predecls2 = [] - for pd in predecls: - if pd not in predecls2: - predecls2.append(pd) - predecls2.sort() - code += "\n".join(predecls2) - code += "\n\n"; - - if base: - code += '#include "params/%s.hh"\n\n' % base - - # Generate declarations for locally defined enumerations. - enum_ptypes = [t for t in ptypes if issubclass(t, Enum)] - if enum_ptypes: - code += "\n".join([t.cxx_decl() for t in enum_ptypes]) - code += "\n\n" - - # now generate the actual param struct - code += "struct %sParams" % cls - if base: - code += " : public %sParams" % base - code += " {\n" - decls = [p.cxx_decl() for p in params] - decls.sort() - code += "".join([" %s\n" % d for d in decls]) - code += "};\n" - - # close #ifndef __PARAMS__* guard - code += "\n#endif\n" - return code - - def swig_decl(cls): - - code = '%%module %sParams\n' % cls - - if str(cls) != 'SimObject': - base = cls.__bases__[0].type - else: - base = None - - # The 'dict' attribute restricts us to the params declared in - # the object itself, not including inherited params (which - # will also be inherited from the base class's param struct - # here). - params = cls._params.dict.values() - ptypes = [p.ptype for p in params] - - # get a list of lists of predeclaration lines - predecls = [p.swig_predecls() for p in params] - # flatten - predecls = reduce(lambda x,y:x+y, predecls, []) - # remove redundant lines - predecls2 = [] - for pd in predecls: - if pd not in predecls2: - predecls2.append(pd) - predecls2.sort() - code += "\n".join(predecls2) - code += "\n\n"; - - if base: - code += '%%import "python/m5/swig/%sParams.i"\n\n' % base - - code += '%{\n' - code += '#include "params/%s.hh"\n' % cls - code += '%}\n\n' - code += '%%include "params/%s.hh"\n\n' % cls - - return code - -# The SimObject class is the root of the special hierarchy. Most of -# the code in this class deals with the configuration hierarchy itself -# (parent/child node relationships). -class SimObject(object): - # Specify metaclass. Any class inheriting from SimObject will - # get this metaclass. - __metaclass__ = MetaSimObject - type = 'SimObject' - - name = Param.String("Object name") - - # Initialize new instance. For objects with SimObject-valued - # children, we need to recursively clone the classes represented - # by those param values as well in a consistent "deep copy"-style - # fashion. That is, we want to make sure that each instance is - # cloned only once, and that if there are multiple references to - # the same original object, we end up with the corresponding - # cloned references all pointing to the same cloned instance. - def __init__(self, **kwargs): - ancestor = kwargs.get('_ancestor') - memo_dict = kwargs.get('_memo') - if memo_dict is None: - # prepare to memoize any recursively instantiated objects - memo_dict = {} - elif ancestor: - # memoize me now to avoid problems with recursive calls - memo_dict[ancestor] = self - - if not ancestor: - ancestor = self.__class__ - ancestor._instantiated = True - - # initialize required attributes - self._parent = None - self._children = {} - self._ccObject = None # pointer to C++ object - self._instantiated = False # really "cloned" - - # Inherit parameter values from class using multidict so - # individual value settings can be overridden. - self._values = multidict(ancestor._values) - # clone SimObject-valued parameters - for key,val in ancestor._values.iteritems(): - if isSimObject(val): - setattr(self, key, val(_memo=memo_dict)) - elif isSimObjectSequence(val) and len(val): - setattr(self, key, [ v(_memo=memo_dict) for v in val ]) - # clone port references. no need to use a multidict here - # since we will be creating new references for all ports. - self._port_map = {} - for key,val in ancestor._port_map.iteritems(): - self._port_map[key] = applyOrMap(val, 'clone', memo_dict) - # apply attribute assignments from keyword args, if any - for key,val in kwargs.iteritems(): - setattr(self, key, val) - - # "Clone" the current instance by creating another instance of - # this instance's class, but that inherits its parameter values - # and port mappings from the current instance. If we're in a - # "deep copy" recursive clone, check the _memo dict to see if - # we've already cloned this instance. - def __call__(self, **kwargs): - memo_dict = kwargs.get('_memo') - if memo_dict is None: - # no memo_dict: must be top-level clone operation. - # this is only allowed at the root of a hierarchy - if self._parent: - raise RuntimeError, "attempt to clone object %s " \ - "not at the root of a tree (parent = %s)" \ - % (self, self._parent) - # create a new dict and use that. - memo_dict = {} - kwargs['_memo'] = memo_dict - elif memo_dict.has_key(self): - # clone already done & memoized - return memo_dict[self] - return self.__class__(_ancestor = self, **kwargs) - - def __getattr__(self, attr): - if self._ports.has_key(attr): - # return reference that can be assigned to another port - # via __setattr__ - return self._ports[attr].makeRef(self, attr) - - if self._values.has_key(attr): - return self._values[attr] - - raise AttributeError, "object '%s' has no attribute '%s'" \ - % (self.__class__.__name__, attr) - - # Set attribute (called on foo.attr = value when foo is an - # instance of class cls). - def __setattr__(self, attr, value): - # normal processing for private attributes - if attr.startswith('_'): - object.__setattr__(self, attr, value) - return - - if self._ports.has_key(attr): - # set up port connection - self._ports[attr].connect(self, attr, value) - return - - if isSimObjectOrSequence(value) and self._instantiated: - raise RuntimeError, \ - "cannot set SimObject parameter '%s' after\n" \ - " instance been cloned %s" % (attr, `self`) - - # must be SimObject param - param = self._params.get(attr, None) - if param: - try: - value = param.convert(value) - except Exception, e: - msg = "%s\nError setting param %s.%s to %s\n" % \ - (e, self.__class__.__name__, attr, value) - e.args = (msg, ) - raise - elif isSimObjectOrSequence(value): - pass - else: - raise AttributeError, "Class %s has no parameter %s" \ - % (self.__class__.__name__, attr) - - # clear out old child with this name, if any - self.clear_child(attr) - - if isSimObject(value): - value.set_path(self, attr) - elif isSimObjectSequence(value): - value = SimObjVector(value) - [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)] - - self._values[attr] = value - - # this hack allows tacking a '[0]' onto parameters that may or may - # not be vectors, and always getting the first element (e.g. cpus) - def __getitem__(self, key): - if key == 0: - return self - raise TypeError, "Non-zero index '%s' to SimObject" % key - - # clear out children with given name, even if it's a vector - def clear_child(self, name): - if not self._children.has_key(name): - return - child = self._children[name] - if isinstance(child, SimObjVector): - for i in xrange(len(child)): - del self._children["s%d" % (name, i)] - del self._children[name] - - def add_child(self, name, value): - self._children[name] = value - - def set_path(self, parent, name): - if not self._parent: - self._parent = parent - self._name = name - parent.add_child(name, self) - - def path(self): - if not self._parent: - return 'root' - ppath = self._parent.path() - if ppath == 'root': - return self._name - return ppath + "." + self._name - - def __str__(self): - return self.path() - - def ini_str(self): - return self.path() - - def find_any(self, ptype): - if isinstance(self, ptype): - return self, True - - found_obj = None - for child in self._children.itervalues(): - if isinstance(child, ptype): - if found_obj != None and child != found_obj: - raise AttributeError, \ - 'parent.any matched more than one: %s %s' % \ - (found_obj.path, child.path) - found_obj = child - # search param space - for pname,pdesc in self._params.iteritems(): - if issubclass(pdesc.ptype, ptype): - match_obj = self._values[pname] - if found_obj != None and found_obj != match_obj: - raise AttributeError, \ - 'parent.any matched more than one: %s' % obj.path - found_obj = match_obj - return found_obj, found_obj != None - - def unproxy(self, base): - return self - - def print_ini(self): - print '[' + self.path() + ']' # .ini section header - - instanceDict[self.path()] = self - - if hasattr(self, 'type') and not isinstance(self, ParamContext): - print 'type=%s' % self.type - - child_names = self._children.keys() - child_names.sort() - np_child_names = [c for c in child_names \ - if not isinstance(self._children[c], ParamContext)] - if len(np_child_names): - print 'children=%s' % ' '.join(np_child_names) - - param_names = self._params.keys() - param_names.sort() - for param in param_names: - value = self._values.get(param, None) - if value != None: - if isproxy(value): - try: - value = value.unproxy(self) - except: - print >> sys.stderr, \ - "Error in unproxying param '%s' of %s" % \ - (param, self.path()) - raise - setattr(self, param, value) - print '%s=%s' % (param, self._values[param].ini_str()) - - print # blank line between objects - - for child in child_names: - self._children[child].print_ini() - - # Call C++ to create C++ object corresponding to this object and - # (recursively) all its children - def createCCObject(self): - self.getCCObject() # force creation - for child in self._children.itervalues(): - child.createCCObject() - - # Get C++ object corresponding to this object, calling C++ if - # necessary to construct it. Does *not* recursively create - # children. - def getCCObject(self): - if not self._ccObject: - self._ccObject = -1 # flag to catch cycles in recursion - self._ccObject = cc_main.createSimObject(self.path()) - elif self._ccObject == -1: - raise RuntimeError, "%s: recursive call to getCCObject()" \ - % self.path() - return self._ccObject - - # Create C++ port connections corresponding to the connections in - # _port_map (& recursively for all children) - def connectPorts(self): - for portRef in self._port_map.itervalues(): - applyOrMap(portRef, 'ccConnect') - for child in self._children.itervalues(): - child.connectPorts() - - def startDrain(self, drain_event, recursive): - count = 0 - # ParamContexts don't serialize - if isinstance(self, SimObject) and not isinstance(self, ParamContext): - count += self._ccObject.drain(drain_event) - if recursive: - for child in self._children.itervalues(): - count += child.startDrain(drain_event, True) - return count - - def resume(self): - if isinstance(self, SimObject) and not isinstance(self, ParamContext): - self._ccObject.resume() - for child in self._children.itervalues(): - child.resume() - - def changeTiming(self, mode): - if isinstance(self, System): - self._ccObject.setMemoryMode(mode) - for child in self._children.itervalues(): - child.changeTiming(mode) - - def takeOverFrom(self, old_cpu): - cpu_ptr = cc_main.convertToBaseCPUPtr(old_cpu._ccObject) - self._ccObject.takeOverFrom(cpu_ptr) - - # generate output file for 'dot' to display as a pretty graph. - # this code is currently broken. - def outputDot(self, dot): - label = "{%s|" % self.path - if isSimObject(self.realtype): - label += '%s|' % self.type - - if self.children: - # instantiate children in same order they were added for - # backward compatibility (else we can end up with cpu1 - # before cpu0). - for c in self.children: - dot.add_edge(pydot.Edge(self.path,c.path, style="bold")) - - simobjs = [] - for param in self.params: - try: - if param.value is None: - raise AttributeError, 'Parameter with no value' - - value = param.value - string = param.string(value) - except Exception, e: - msg = 'exception in %s:%s\n%s' % (self.name, param.name, e) - e.args = (msg, ) - raise - - if isSimObject(param.ptype) and string != "Null": - simobjs.append(string) - else: - label += '%s = %s\\n' % (param.name, string) - - for so in simobjs: - label += "|<%s> %s" % (so, so) - dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, - tailport="w")) - label += '}' - dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label)) - - # recursively dump out children - for c in self.children: - c.outputDot(dot) - -class ParamContext(SimObject): - pass - -##################################################################### -# -# Proxy object support. -# -##################################################################### - -class BaseProxy(object): - def __init__(self, search_self, search_up): - self._search_self = search_self - self._search_up = search_up - self._multiplier = None - - def __setattr__(self, attr, value): - if not attr.startswith('_'): - raise AttributeError, 'cannot set attribute on proxy object' - super(BaseProxy, self).__setattr__(attr, value) - - # support multiplying proxies by constants - def __mul__(self, other): - if not isinstance(other, (int, long, float)): - raise TypeError, "Proxy multiplier must be integer" - if self._multiplier == None: - self._multiplier = other - else: - # support chained multipliers - self._multiplier *= other - return self - - __rmul__ = __mul__ - - def _mulcheck(self, result): - if self._multiplier == None: - return result - return result * self._multiplier - - def unproxy(self, base): - obj = base - done = False - - if self._search_self: - result, done = self.find(obj) - - if self._search_up: - while not done: - obj = obj._parent - if not obj: - break - result, done = self.find(obj) - - if not done: - raise AttributeError, \ - "Can't resolve proxy '%s' of type '%s' from '%s'" % \ - (self.path(), self._pdesc.ptype_str, base.path()) - - if isinstance(result, BaseProxy): - if result == self: - raise RuntimeError, "Cycle in unproxy" - result = result.unproxy(obj) - - return self._mulcheck(result) - - def getindex(obj, index): - if index == None: - return obj - try: - obj = obj[index] - except TypeError: - if index != 0: - raise - # if index is 0 and item is not subscriptable, just - # use item itself (so cpu[0] works on uniprocessors) - return obj - getindex = staticmethod(getindex) - - def set_param_desc(self, pdesc): - self._pdesc = pdesc - -class AttrProxy(BaseProxy): - def __init__(self, search_self, search_up, attr): - super(AttrProxy, self).__init__(search_self, search_up) - self._attr = attr - self._modifiers = [] - - def __getattr__(self, attr): - # python uses __bases__ internally for inheritance - if attr.startswith('_'): - return super(AttrProxy, self).__getattr__(self, attr) - if hasattr(self, '_pdesc'): - raise AttributeError, "Attribute reference on bound proxy" - self._modifiers.append(attr) - return self - - # support indexing on proxies (e.g., Self.cpu[0]) - def __getitem__(self, key): - if not isinstance(key, int): - raise TypeError, "Proxy object requires integer index" - self._modifiers.append(key) - return self - - def find(self, obj): - try: - val = getattr(obj, self._attr) - except: - return None, False - while isproxy(val): - val = val.unproxy(obj) - for m in self._modifiers: - if isinstance(m, str): - val = getattr(val, m) - elif isinstance(m, int): - val = val[m] - else: - assert("Item must be string or integer") - while isproxy(val): - val = val.unproxy(obj) - return val, True - - def path(self): - p = self._attr - for m in self._modifiers: - if isinstance(m, str): - p += '.%s' % m - elif isinstance(m, int): - p += '[%d]' % m - else: - assert("Item must be string or integer") - return p - -class AnyProxy(BaseProxy): - def find(self, obj): - return obj.find_any(self._pdesc.ptype) - - def path(self): - return 'any' - -def isproxy(obj): - if isinstance(obj, (BaseProxy, EthernetAddr)): - return True - elif isinstance(obj, (list, tuple)): - for v in obj: - if isproxy(v): - return True - return False - -class ProxyFactory(object): - def __init__(self, search_self, search_up): - self.search_self = search_self - self.search_up = search_up - - def __getattr__(self, attr): - if attr == 'any': - return AnyProxy(self.search_self, self.search_up) - else: - return AttrProxy(self.search_self, self.search_up, attr) - -# global objects for handling proxies -Parent = ProxyFactory(search_self = False, search_up = True) -Self = ProxyFactory(search_self = True, search_up = False) - -##################################################################### -# -# Parameter description classes -# -# The _params dictionary in each class maps parameter names to either -# a Param or a VectorParam object. These objects contain the -# parameter description string, the parameter type, and the default -# value (if any). The convert() method on these objects is used to -# force whatever value is assigned to the parameter to the appropriate -# type. -# -# Note that the default values are loaded into the class's attribute -# space when the parameter dictionary is initialized (in -# MetaSimObject._new_param()); after that point they aren't used. -# -##################################################################### - -# Dummy base class to identify types that are legitimate for SimObject -# parameters. -class ParamValue(object): - - cxx_predecls = [] - swig_predecls = [] - - # default for printing to .ini file is regular string conversion. - # will be overridden in some cases - def ini_str(self): - return str(self) - - # allows us to blithely call unproxy() on things without checking - # if they're really proxies or not - def unproxy(self, base): - return self - -# Regular parameter description. -class ParamDesc(object): - def __init__(self, ptype_str, ptype, *args, **kwargs): - self.ptype_str = ptype_str - # remember ptype only if it is provided - if ptype != None: - self.ptype = ptype - - if args: - if len(args) == 1: - self.desc = args[0] - elif len(args) == 2: - self.default = args[0] - self.desc = args[1] - else: - raise TypeError, 'too many arguments' - - if kwargs.has_key('desc'): - assert(not hasattr(self, 'desc')) - self.desc = kwargs['desc'] - del kwargs['desc'] - - if kwargs.has_key('default'): - assert(not hasattr(self, 'default')) - self.default = kwargs['default'] - del kwargs['default'] - - if kwargs: - raise TypeError, 'extra unknown kwargs %s' % kwargs - - if not hasattr(self, 'desc'): - raise TypeError, 'desc attribute missing' - - def __getattr__(self, attr): - if attr == 'ptype': - try: - ptype = eval(self.ptype_str, m5.objects.__dict__) - if not isinstance(ptype, type): - panic("Param qualifier is not a type: %s" % self.ptype) - self.ptype = ptype - return ptype - except NameError: - pass - raise AttributeError, "'%s' object has no attribute '%s'" % \ - (type(self).__name__, attr) - - def convert(self, value): - if isinstance(value, BaseProxy): - value.set_param_desc(self) - return value - if not hasattr(self, 'ptype') and isNullPointer(value): - # deferred evaluation of SimObject; continue to defer if - # we're just assigning a null pointer - return value - if isinstance(value, self.ptype): - return value - if isNullPointer(value) and issubclass(self.ptype, SimObject): - return value - return self.ptype(value) - - def cxx_predecls(self): - return self.ptype.cxx_predecls - - def swig_predecls(self): - return self.ptype.swig_predecls - - def cxx_decl(self): - return '%s %s;' % (self.ptype.cxx_type, self.name) - -# Vector-valued parameter description. Just like ParamDesc, except -# that the value is a vector (list) of the specified type instead of a -# single value. - -class VectorParamValue(list): - def ini_str(self): - return ' '.join([v.ini_str() for v in self]) - - def unproxy(self, base): - return [v.unproxy(base) for v in self] - -class SimObjVector(VectorParamValue): - def print_ini(self): - for v in self: - v.print_ini() - -class VectorParamDesc(ParamDesc): - # Convert assigned value to appropriate type. If the RHS is not a - # list or tuple, it generates a single-element list. - def convert(self, value): - if isinstance(value, (list, tuple)): - # list: coerce each element into new list - tmp_list = [ ParamDesc.convert(self, v) for v in value ] - if isSimObjectSequence(tmp_list): - return SimObjVector(tmp_list) - else: - return VectorParamValue(tmp_list) - else: - # singleton: leave it be (could coerce to a single-element - # list here, but for some historical reason we don't... - return ParamDesc.convert(self, value) - - def cxx_predecls(self): - return ['#include '] + self.ptype.cxx_predecls - - def swig_predecls(self): - return ['%include "std_vector.i"'] + self.ptype.swig_predecls - - def cxx_decl(self): - return 'std::vector< %s > %s;' % (self.ptype.cxx_type, self.name) - -class ParamFactory(object): - def __init__(self, param_desc_class, ptype_str = None): - self.param_desc_class = param_desc_class - self.ptype_str = ptype_str - - def __getattr__(self, attr): - if self.ptype_str: - attr = self.ptype_str + '.' + attr - return ParamFactory(self.param_desc_class, attr) - - # E.g., Param.Int(5, "number of widgets") - def __call__(self, *args, **kwargs): - caller_frame = inspect.currentframe().f_back - ptype = None - try: - ptype = eval(self.ptype_str, - caller_frame.f_globals, caller_frame.f_locals) - if not isinstance(ptype, type): - raise TypeError, \ - "Param qualifier is not a type: %s" % ptype - except NameError: - # if name isn't defined yet, assume it's a SimObject, and - # try to resolve it later - pass - return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs) - -Param = ParamFactory(ParamDesc) -VectorParam = ParamFactory(VectorParamDesc) - -# String-valued parameter. Just mixin the ParamValue class -# with the built-in str class. -class String(ParamValue,str): - cxx_type = 'std::string' - cxx_predecls = ['#include '] - swig_predecls = ['%include "std_string.i"\n' + - '%apply const std::string& {std::string *};'] - pass - -##################################################################### -# -# Parameter Types -# -# Though native Python types could be used to specify parameter types -# (the 'ptype' field of the Param and VectorParam classes), it's more -# flexible to define our own set of types. This gives us more control -# over how Python expressions are converted to values (via the -# __init__() constructor) and how these values are printed out (via -# the __str__() conversion method). Eventually we'll need these types -# to correspond to distinct C++ types as well. -# -##################################################################### - -# superclass for "numeric" parameter values, to emulate math -# operations in a type-safe way. e.g., a Latency times an int returns -# a new Latency object. -class NumericParamValue(ParamValue): - def __str__(self): - return str(self.value) - - def __float__(self): - return float(self.value) - - # hook for bounds checking - def _check(self): - return - - def __mul__(self, other): - newobj = self.__class__(self) - newobj.value *= other - newobj._check() - return newobj - - __rmul__ = __mul__ - - def __div__(self, other): - newobj = self.__class__(self) - newobj.value /= other - newobj._check() - return newobj - - def __sub__(self, other): - newobj = self.__class__(self) - newobj.value -= other - newobj._check() - return newobj - -# Metaclass for bounds-checked integer parameters. See CheckedInt. -class CheckedIntType(type): - def __init__(cls, name, bases, dict): - super(CheckedIntType, cls).__init__(name, bases, dict) - - # CheckedInt is an abstract base class, so we actually don't - # want to do any processing on it... the rest of this code is - # just for classes that derive from CheckedInt. - if name == 'CheckedInt': - return - - if not cls.cxx_predecls: - # most derived types require this, so we just do it here once - cls.cxx_predecls = ['#include "sim/host.hh"'] - - if not cls.swig_predecls: - # most derived types require this, so we just do it here once - cls.swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + - '%import "sim/host.hh"'] - - if not (hasattr(cls, 'min') and hasattr(cls, 'max')): - if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')): - panic("CheckedInt subclass %s must define either\n" \ - " 'min' and 'max' or 'size' and 'unsigned'\n" \ - % name); - if cls.unsigned: - cls.min = 0 - cls.max = 2 ** cls.size - 1 - else: - cls.min = -(2 ** (cls.size - 1)) - cls.max = (2 ** (cls.size - 1)) - 1 - -# Abstract superclass for bounds-checked integer parameters. This -# class is subclassed to generate parameter classes with specific -# bounds. Initialization of the min and max bounds is done in the -# metaclass CheckedIntType.__init__. -class CheckedInt(NumericParamValue): - __metaclass__ = CheckedIntType - - def _check(self): - if not self.min <= self.value <= self.max: - raise TypeError, 'Integer param out of bounds %d < %d < %d' % \ - (self.min, self.value, self.max) - - def __init__(self, value): - if isinstance(value, str): - self.value = toInteger(value) - elif isinstance(value, (int, long, float)): - self.value = long(value) - self._check() - -class Int(CheckedInt): cxx_type = 'int'; size = 32; unsigned = False -class Unsigned(CheckedInt): cxx_type = 'unsigned'; size = 32; unsigned = True - -class Int8(CheckedInt): cxx_type = 'int8_t'; size = 8; unsigned = False -class UInt8(CheckedInt): cxx_type = 'uint8_t'; size = 8; unsigned = True -class Int16(CheckedInt): cxx_type = 'int16_t'; size = 16; unsigned = False -class UInt16(CheckedInt): cxx_type = 'uint16_t'; size = 16; unsigned = True -class Int32(CheckedInt): cxx_type = 'int32_t'; size = 32; unsigned = False -class UInt32(CheckedInt): cxx_type = 'uint32_t'; size = 32; unsigned = True -class Int64(CheckedInt): cxx_type = 'int64_t'; size = 64; unsigned = False -class UInt64(CheckedInt): cxx_type = 'uint64_t'; size = 64; unsigned = True - -class Counter(CheckedInt): cxx_type = 'Counter'; size = 64; unsigned = True -class Tick(CheckedInt): cxx_type = 'Tick'; size = 64; unsigned = True -class TcpPort(CheckedInt): cxx_type = 'uint16_t'; size = 16; unsigned = True -class UdpPort(CheckedInt): cxx_type = 'uint16_t'; size = 16; unsigned = True - -class Percent(CheckedInt): cxx_type = 'int'; min = 0; max = 100 - -class Float(ParamValue, float): - pass - -class MemorySize(CheckedInt): - cxx_type = 'uint64_t' - size = 64 - unsigned = True - def __init__(self, value): - if isinstance(value, MemorySize): - self.value = value.value - else: - self.value = toMemorySize(value) - self._check() - -class MemorySize32(CheckedInt): - size = 32 - unsigned = True - def __init__(self, value): - if isinstance(value, MemorySize): - self.value = value.value - else: - self.value = toMemorySize(value) - self._check() - -class Addr(CheckedInt): - cxx_type = 'Addr' - cxx_predecls = ['#include "targetarch/isa_traits.hh"'] - size = 64 - unsigned = True - def __init__(self, value): - if isinstance(value, Addr): - self.value = value.value - else: - try: - self.value = toMemorySize(value) - except TypeError: - self.value = long(value) - self._check() - - -class MetaRange(type): - def __init__(cls, name, bases, dict): - super(MetaRange, cls).__init__(name, bases, dict) - if name == 'Range': - return - cls.cxx_type = 'Range< %s >' % cls.type.cxx_type - cls.cxx_predecls = \ - ['#include "base/range.hh"'] + cls.type.cxx_predecls - -class Range(ParamValue): - __metaclass__ = MetaRange - type = Int # default; can be overridden in subclasses - def __init__(self, *args, **kwargs): - def handle_kwargs(self, kwargs): - if 'end' in kwargs: - self.second = self.type(kwargs.pop('end')) - elif 'size' in kwargs: - self.second = self.first + self.type(kwargs.pop('size')) - 1 - else: - raise TypeError, "Either end or size must be specified" - - if len(args) == 0: - self.first = self.type(kwargs.pop('start')) - handle_kwargs(self, kwargs) - - elif len(args) == 1: - if kwargs: - self.first = self.type(args[0]) - handle_kwargs(self, kwargs) - elif isinstance(args[0], Range): - self.first = self.type(args[0].first) - self.second = self.type(args[0].second) - else: - self.first = self.type(0) - self.second = self.type(args[0]) - 1 - - elif len(args) == 2: - self.first = self.type(args[0]) - self.second = self.type(args[1]) - else: - raise TypeError, "Too many arguments specified" - - if kwargs: - raise TypeError, "too many keywords: %s" % kwargs.keys() - - def __str__(self): - return '%s:%s' % (self.first, self.second) - -class AddrRange(Range): - type = Addr - -class TickRange(Range): - type = Tick - -# Boolean parameter type. Python doesn't let you subclass bool, since -# it doesn't want to let you create multiple instances of True and -# False. Thus this is a little more complicated than String. -class Bool(ParamValue): - cxx_type = 'bool' - def __init__(self, value): - try: - self.value = toBool(value) - except TypeError: - self.value = bool(value) - - def __str__(self): - return str(self.value) - - def ini_str(self): - if self.value: - return 'true' - return 'false' - -def IncEthernetAddr(addr, val = 1): - bytes = map(lambda x: int(x, 16), addr.split(':')) - bytes[5] += val - for i in (5, 4, 3, 2, 1): - val,rem = divmod(bytes[i], 256) - bytes[i] = rem - if val == 0: - break - bytes[i - 1] += val - assert(bytes[0] <= 255) - return ':'.join(map(lambda x: '%02x' % x, bytes)) - -class NextEthernetAddr(object): - addr = "00:90:00:00:00:01" - - def __init__(self, inc = 1): - self.value = NextEthernetAddr.addr - NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc) - -class EthernetAddr(ParamValue): - cxx_type = 'Net::EthAddr' - cxx_predecls = ['#include "base/inet.hh"'] - swig_predecls = ['class Net::EthAddr;'] - def __init__(self, value): - if value == NextEthernetAddr: - self.value = value - return - - if not isinstance(value, str): - raise TypeError, "expected an ethernet address and didn't get one" - - bytes = value.split(':') - if len(bytes) != 6: - raise TypeError, 'invalid ethernet address %s' % value - - for byte in bytes: - if not 0 <= int(byte) <= 256: - raise TypeError, 'invalid ethernet address %s' % value - - self.value = value - - def unproxy(self, base): - if self.value == NextEthernetAddr: - self.addr = self.value().value - return self - - def __str__(self): - if self.value == NextEthernetAddr: - if hasattr(self, 'addr'): - return self.addr - else: - return "NextEthernetAddr (unresolved)" - else: - return self.value - -# Special class for NULL pointers. Note the special check in -# make_param_value() above that lets these be assigned where a -# SimObject is required. -# only one copy of a particular node -class NullSimObject(object): - __metaclass__ = Singleton - - def __call__(cls): - return cls - - def _instantiate(self, parent = None, path = ''): - pass - - def ini_str(self): - return 'Null' - - def unproxy(self, base): - return self - - def set_path(self, parent, name): - pass - def __str__(self): - return 'Null' - -# The only instance you'll ever need... -Null = NULL = NullSimObject() - -# Enumerated types are a little more complex. The user specifies the -# type as Enum(foo) where foo is either a list or dictionary of -# alternatives (typically strings, but not necessarily so). (In the -# long run, the integer value of the parameter will be the list index -# or the corresponding dictionary value. For now, since we only check -# that the alternative is valid and then spit it into a .ini file, -# there's not much point in using the dictionary.) - -# What Enum() must do is generate a new type encapsulating the -# provided list/dictionary so that specific values of the parameter -# can be instances of that type. We define two hidden internal -# classes (_ListEnum and _DictEnum) to serve as base classes, then -# derive the new type from the appropriate base class on the fly. - - -# Metaclass for Enum types -class MetaEnum(type): - def __init__(cls, name, bases, init_dict): - if init_dict.has_key('map'): - if not isinstance(cls.map, dict): - raise TypeError, "Enum-derived class attribute 'map' " \ - "must be of type dict" - # build list of value strings from map - cls.vals = cls.map.keys() - cls.vals.sort() - elif init_dict.has_key('vals'): - if not isinstance(cls.vals, list): - raise TypeError, "Enum-derived class attribute 'vals' " \ - "must be of type list" - # build string->value map from vals sequence - cls.map = {} - for idx,val in enumerate(cls.vals): - cls.map[val] = idx - else: - raise TypeError, "Enum-derived class must define "\ - "attribute 'map' or 'vals'" - - cls.cxx_type = name + '::Enum' - - super(MetaEnum, cls).__init__(name, bases, init_dict) - - # Generate C++ class declaration for this enum type. - # Note that we wrap the enum in a class/struct to act as a namespace, - # so that the enum strings can be brief w/o worrying about collisions. - def cxx_decl(cls): - s = 'struct %s {\n enum Enum {\n ' % cls.__name__ - s += ',\n '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals]) - s += '\n };\n};\n' - return s - -# Base class for enum types. -class Enum(ParamValue): - __metaclass__ = MetaEnum - vals = [] - - def __init__(self, value): - if value not in self.map: - raise TypeError, "Enum param got bad value '%s' (not in %s)" \ - % (value, self.vals) - self.value = value - - def __str__(self): - return self.value - -ticks_per_sec = None - -# how big does a rounding error need to be before we warn about it? -frequency_tolerance = 0.001 # 0.1% - -# convert a floting-point # of ticks to integer, and warn if rounding -# discards too much precision -def tick_check(float_ticks): - if float_ticks == 0: - return 0 - int_ticks = int(round(float_ticks)) - err = (float_ticks - int_ticks) / float_ticks - if err > frequency_tolerance: - print >> sys.stderr, "Warning: rounding error > tolerance" - print >> sys.stderr, " %f rounded to %d" % (float_ticks, int_ticks) - #raise ValueError - return int_ticks - -def getLatency(value): - if isinstance(value, Latency) or isinstance(value, Clock): - return value.value - elif isinstance(value, Frequency) or isinstance(value, RootClock): - return 1 / value.value - elif isinstance(value, str): - try: - return toLatency(value) - except ValueError: - try: - return 1 / toFrequency(value) - except ValueError: - pass # fall through - raise ValueError, "Invalid Frequency/Latency value '%s'" % value - - -class Latency(NumericParamValue): - cxx_type = 'Tick' - cxx_predecls = ['#include "sim/host.hh"'] - swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + - '%import "sim/host.hh"'] - def __init__(self, value): - self.value = getLatency(value) - - def __getattr__(self, attr): - if attr in ('latency', 'period'): - return self - if attr == 'frequency': - return Frequency(self) - raise AttributeError, "Latency object has no attribute '%s'" % attr - - # convert latency to ticks - def ini_str(self): - return str(tick_check(self.value * ticks_per_sec)) - -class Frequency(NumericParamValue): - cxx_type = 'Tick' - cxx_predecls = ['#include "sim/host.hh"'] - swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + - '%import "sim/host.hh"'] - def __init__(self, value): - self.value = 1 / getLatency(value) - - def __getattr__(self, attr): - if attr == 'frequency': - return self - if attr in ('latency', 'period'): - return Latency(self) - raise AttributeError, "Frequency object has no attribute '%s'" % attr - - # convert frequency to ticks per period - def ini_str(self): - return self.period.ini_str() - -# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz). -# We can't inherit from Frequency because we don't want it to be directly -# assignable to a regular Frequency parameter. -class RootClock(ParamValue): - cxx_type = 'Tick' - cxx_predecls = ['#include "sim/host.hh"'] - swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + - '%import "sim/host.hh"'] - def __init__(self, value): - self.value = 1 / getLatency(value) - - def __getattr__(self, attr): - if attr == 'frequency': - return Frequency(self) - if attr in ('latency', 'period'): - return Latency(self) - raise AttributeError, "Frequency object has no attribute '%s'" % attr - - def ini_str(self): - return str(tick_check(self.value)) - -# A generic frequency and/or Latency value. Value is stored as a latency, -# but to avoid ambiguity this object does not support numeric ops (* or /). -# An explicit conversion to a Latency or Frequency must be made first. -class Clock(ParamValue): - cxx_type = 'Tick' - cxx_predecls = ['#include "sim/host.hh"'] - swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + - '%import "sim/host.hh"'] - def __init__(self, value): - self.value = getLatency(value) - - def __getattr__(self, attr): - if attr == 'frequency': - return Frequency(self) - if attr in ('latency', 'period'): - return Latency(self) - raise AttributeError, "Frequency object has no attribute '%s'" % attr - - def ini_str(self): - return self.period.ini_str() - -class NetworkBandwidth(float,ParamValue): - cxx_type = 'float' - def __new__(cls, value): - val = toNetworkBandwidth(value) / 8.0 - return super(cls, NetworkBandwidth).__new__(cls, val) - - def __str__(self): - return str(self.val) - - def ini_str(self): - return '%f' % (ticks_per_sec / float(self)) - -class MemoryBandwidth(float,ParamValue): - cxx_type = 'float' - def __new__(self, value): - val = toMemoryBandwidth(value) - return super(cls, MemoryBandwidth).__new__(cls, val) - - def __str__(self): - return str(self.val) - - def ini_str(self): - return '%f' % (ticks_per_sec / float(self)) - -# -# "Constants"... handy aliases for various values. -# - -# Some memory range specifications use this as a default upper bound. -MaxAddr = Addr.max -MaxTick = Tick.max -AllMemory = AddrRange(0, MaxAddr) - - -##################################################################### -# -# Port objects -# -# Ports are used to interconnect objects in the memory system. -# -##################################################################### - -# Port reference: encapsulates a reference to a particular port on a -# particular SimObject. -class PortRef(object): - def __init__(self, simobj, name, isVec): - assert(isSimObject(simobj)) - self.simobj = simobj - self.name = name - self.index = -1 - self.isVec = isVec # is this a vector port? - self.peer = None # not associated with another port yet - self.ccConnected = False # C++ port connection done? - - # Set peer port reference. Called via __setattr__ as a result of - # a port assignment, e.g., "obj1.port1 = obj2.port2". - def setPeer(self, other): - if self.isVec: - curMap = self.simobj._port_map.get(self.name, []) - self.index = len(curMap) - curMap.append(other) - else: - curMap = self.simobj._port_map.get(self.name) - if curMap and not self.isVec: - print "warning: overwriting port", self.simobj, self.name - curMap = other - self.simobj._port_map[self.name] = curMap - self.peer = other - - def clone(self, memo): - newRef = copy.copy(self) - assert(isSimObject(newRef.simobj)) - newRef.simobj = newRef.simobj(_memo=memo) - # Tricky: if I'm the *second* PortRef in the pair to be - # cloned, then my peer is still in the middle of its clone - # method, and thus hasn't returned to its owner's - # SimObject.__init__ to get installed in _port_map. As a - # result I have no way of finding the *new* peer object. So I - # mark myself as "waiting" for my peer, and I let the *first* - # PortRef clone call set up both peer pointers after I return. - newPeer = newRef.simobj._port_map.get(self.name) - if newPeer: - if self.isVec: - assert(self.index != -1) - newPeer = newPeer[self.index] - # other guy is all set up except for his peer pointer - assert(newPeer.peer == -1) # peer must be waiting for handshake - newPeer.peer = newRef - newRef.peer = newPeer - else: - # other guy is in clone; just wait for him to do the work - newRef.peer = -1 # mark as waiting for handshake - return newRef - - # Call C++ to create corresponding port connection between C++ objects - def ccConnect(self): - if self.ccConnected: # already done this - return - peer = self.peer - cc_main.connectPorts(self.simobj.getCCObject(), self.name, self.index, - peer.simobj.getCCObject(), peer.name, peer.index) - self.ccConnected = True - peer.ccConnected = True - -# Port description object. Like a ParamDesc object, this represents a -# logical port in the SimObject class, not a particular port on a -# SimObject instance. The latter are represented by PortRef objects. -class Port(object): - def __init__(self, desc): - self.desc = desc - self.isVec = False - - # Generate a PortRef for this port on the given SimObject with the - # given name - def makeRef(self, simobj, name): - return PortRef(simobj, name, self.isVec) - - # Connect an instance of this port (on the given SimObject with - # the given name) with the port described by the supplied PortRef - def connect(self, simobj, name, ref): - if not isinstance(ref, PortRef): - raise TypeError, \ - "assigning non-port reference port '%s'" % name - myRef = self.makeRef(simobj, name) - myRef.setPeer(ref) - ref.setPeer(myRef) - -# VectorPort description object. Like Port, but represents a vector -# of connections (e.g., as on a Bus). -class VectorPort(Port): - def __init__(self, desc): - Port.__init__(self, desc) - self.isVec = True - -##################################################################### - -# __all__ defines the list of symbols that get exported when -# 'from config import *' is invoked. Try to keep this reasonably -# short to avoid polluting other namespaces. -__all__ = ['SimObject', 'ParamContext', 'Param', 'VectorParam', - 'Parent', 'Self', - 'Enum', 'Bool', 'String', 'Float', - 'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16', - 'Int32', 'UInt32', 'Int64', 'UInt64', - 'Counter', 'Addr', 'Tick', 'Percent', - 'TcpPort', 'UdpPort', 'EthernetAddr', - 'MemorySize', 'MemorySize32', - 'Latency', 'Frequency', 'RootClock', 'Clock', - 'NetworkBandwidth', 'MemoryBandwidth', - 'Range', 'AddrRange', 'TickRange', - 'MaxAddr', 'MaxTick', 'AllMemory', - 'Null', 'NULL', - 'NextEthernetAddr', - 'Port', 'VectorPort'] - diff --git a/src/python/m5/params.py b/src/python/m5/params.py new file mode 100644 index 000000000..eb30e5c4d --- /dev/null +++ b/src/python/m5/params.py @@ -0,0 +1,831 @@ +# Copyright (c) 2004-2006 The Regents of The University of Michigan +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Steve Reinhardt +# Nathan Binkert + +##################################################################### +# +# Parameter description classes +# +# The _params dictionary in each class maps parameter names to either +# a Param or a VectorParam object. These objects contain the +# parameter description string, the parameter type, and the default +# value (if any). The convert() method on these objects is used to +# force whatever value is assigned to the parameter to the appropriate +# type. +# +# Note that the default values are loaded into the class's attribute +# space when the parameter dictionary is initialized (in +# MetaSimObject._new_param()); after that point they aren't used. +# +##################################################################### + +import sys, inspect, copy +import convert + +# Dummy base class to identify types that are legitimate for SimObject +# parameters. +class ParamValue(object): + + cxx_predecls = [] + swig_predecls = [] + + # default for printing to .ini file is regular string conversion. + # will be overridden in some cases + def ini_str(self): + return str(self) + + # allows us to blithely call unproxy() on things without checking + # if they're really proxies or not + def unproxy(self, base): + return self + +# Regular parameter description. +class ParamDesc(object): + def __init__(self, ptype_str, ptype, *args, **kwargs): + self.ptype_str = ptype_str + # remember ptype only if it is provided + if ptype != None: + self.ptype = ptype + + if args: + if len(args) == 1: + self.desc = args[0] + elif len(args) == 2: + self.default = args[0] + self.desc = args[1] + else: + raise TypeError, 'too many arguments' + + if kwargs.has_key('desc'): + assert(not hasattr(self, 'desc')) + self.desc = kwargs['desc'] + del kwargs['desc'] + + if kwargs.has_key('default'): + assert(not hasattr(self, 'default')) + self.default = kwargs['default'] + del kwargs['default'] + + if kwargs: + raise TypeError, 'extra unknown kwargs %s' % kwargs + + if not hasattr(self, 'desc'): + raise TypeError, 'desc attribute missing' + + def __getattr__(self, attr): + if attr == 'ptype': + try: + ptype = eval(self.ptype_str, m5.objects.__dict__) + if not isinstance(ptype, type): + panic("Param qualifier is not a type: %s" % self.ptype) + self.ptype = ptype + return ptype + except NameError: + pass + raise AttributeError, "'%s' object has no attribute '%s'" % \ + (type(self).__name__, attr) + + def convert(self, value): + if isinstance(value, proxy.BaseProxy): + value.set_param_desc(self) + return value + if not hasattr(self, 'ptype') and isNullPointer(value): + # deferred evaluation of SimObject; continue to defer if + # we're just assigning a null pointer + return value + if isinstance(value, self.ptype): + return value + if isNullPointer(value) and issubclass(self.ptype, SimObject): + return value + return self.ptype(value) + + def cxx_predecls(self): + return self.ptype.cxx_predecls + + def swig_predecls(self): + return self.ptype.swig_predecls + + def cxx_decl(self): + return '%s %s;' % (self.ptype.cxx_type, self.name) + +# Vector-valued parameter description. Just like ParamDesc, except +# that the value is a vector (list) of the specified type instead of a +# single value. + +class VectorParamValue(list): + def ini_str(self): + return ' '.join([v.ini_str() for v in self]) + + def unproxy(self, base): + return [v.unproxy(base) for v in self] + +class SimObjVector(VectorParamValue): + def print_ini(self): + for v in self: + v.print_ini() + +class VectorParamDesc(ParamDesc): + # Convert assigned value to appropriate type. If the RHS is not a + # list or tuple, it generates a single-element list. + def convert(self, value): + if isinstance(value, (list, tuple)): + # list: coerce each element into new list + tmp_list = [ ParamDesc.convert(self, v) for v in value ] + if isSimObjectSequence(tmp_list): + return SimObjVector(tmp_list) + else: + return VectorParamValue(tmp_list) + else: + # singleton: leave it be (could coerce to a single-element + # list here, but for some historical reason we don't... + return ParamDesc.convert(self, value) + + def cxx_predecls(self): + return ['#include '] + self.ptype.cxx_predecls + + def swig_predecls(self): + return ['%include "std_vector.i"'] + self.ptype.swig_predecls + + def cxx_decl(self): + return 'std::vector< %s > %s;' % (self.ptype.cxx_type, self.name) + +class ParamFactory(object): + def __init__(self, param_desc_class, ptype_str = None): + self.param_desc_class = param_desc_class + self.ptype_str = ptype_str + + def __getattr__(self, attr): + if self.ptype_str: + attr = self.ptype_str + '.' + attr + return ParamFactory(self.param_desc_class, attr) + + # E.g., Param.Int(5, "number of widgets") + def __call__(self, *args, **kwargs): + caller_frame = inspect.currentframe().f_back + ptype = None + try: + ptype = eval(self.ptype_str, + caller_frame.f_globals, caller_frame.f_locals) + if not isinstance(ptype, type): + raise TypeError, \ + "Param qualifier is not a type: %s" % ptype + except NameError: + # if name isn't defined yet, assume it's a SimObject, and + # try to resolve it later + pass + return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs) + +Param = ParamFactory(ParamDesc) +VectorParam = ParamFactory(VectorParamDesc) + +##################################################################### +# +# Parameter Types +# +# Though native Python types could be used to specify parameter types +# (the 'ptype' field of the Param and VectorParam classes), it's more +# flexible to define our own set of types. This gives us more control +# over how Python expressions are converted to values (via the +# __init__() constructor) and how these values are printed out (via +# the __str__() conversion method). +# +##################################################################### + +# String-valued parameter. Just mixin the ParamValue class with the +# built-in str class. +class String(ParamValue,str): + cxx_type = 'std::string' + cxx_predecls = ['#include '] + swig_predecls = ['%include "std_string.i"\n' + + '%apply const std::string& {std::string *};'] + pass + +# superclass for "numeric" parameter values, to emulate math +# operations in a type-safe way. e.g., a Latency times an int returns +# a new Latency object. +class NumericParamValue(ParamValue): + def __str__(self): + return str(self.value) + + def __float__(self): + return float(self.value) + + # hook for bounds checking + def _check(self): + return + + def __mul__(self, other): + newobj = self.__class__(self) + newobj.value *= other + newobj._check() + return newobj + + __rmul__ = __mul__ + + def __div__(self, other): + newobj = self.__class__(self) + newobj.value /= other + newobj._check() + return newobj + + def __sub__(self, other): + newobj = self.__class__(self) + newobj.value -= other + newobj._check() + return newobj + +# Metaclass for bounds-checked integer parameters. See CheckedInt. +class CheckedIntType(type): + def __init__(cls, name, bases, dict): + super(CheckedIntType, cls).__init__(name, bases, dict) + + # CheckedInt is an abstract base class, so we actually don't + # want to do any processing on it... the rest of this code is + # just for classes that derive from CheckedInt. + if name == 'CheckedInt': + return + + if not cls.cxx_predecls: + # most derived types require this, so we just do it here once + cls.cxx_predecls = ['#include "sim/host.hh"'] + + if not cls.swig_predecls: + # most derived types require this, so we just do it here once + cls.swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + + '%import "sim/host.hh"'] + + if not (hasattr(cls, 'min') and hasattr(cls, 'max')): + if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')): + panic("CheckedInt subclass %s must define either\n" \ + " 'min' and 'max' or 'size' and 'unsigned'\n" \ + % name); + if cls.unsigned: + cls.min = 0 + cls.max = 2 ** cls.size - 1 + else: + cls.min = -(2 ** (cls.size - 1)) + cls.max = (2 ** (cls.size - 1)) - 1 + +# Abstract superclass for bounds-checked integer parameters. This +# class is subclassed to generate parameter classes with specific +# bounds. Initialization of the min and max bounds is done in the +# metaclass CheckedIntType.__init__. +class CheckedInt(NumericParamValue): + __metaclass__ = CheckedIntType + + def _check(self): + if not self.min <= self.value <= self.max: + raise TypeError, 'Integer param out of bounds %d < %d < %d' % \ + (self.min, self.value, self.max) + + def __init__(self, value): + if isinstance(value, str): + self.value = toInteger(value) + elif isinstance(value, (int, long, float)): + self.value = long(value) + self._check() + +class Int(CheckedInt): cxx_type = 'int'; size = 32; unsigned = False +class Unsigned(CheckedInt): cxx_type = 'unsigned'; size = 32; unsigned = True + +class Int8(CheckedInt): cxx_type = 'int8_t'; size = 8; unsigned = False +class UInt8(CheckedInt): cxx_type = 'uint8_t'; size = 8; unsigned = True +class Int16(CheckedInt): cxx_type = 'int16_t'; size = 16; unsigned = False +class UInt16(CheckedInt): cxx_type = 'uint16_t'; size = 16; unsigned = True +class Int32(CheckedInt): cxx_type = 'int32_t'; size = 32; unsigned = False +class UInt32(CheckedInt): cxx_type = 'uint32_t'; size = 32; unsigned = True +class Int64(CheckedInt): cxx_type = 'int64_t'; size = 64; unsigned = False +class UInt64(CheckedInt): cxx_type = 'uint64_t'; size = 64; unsigned = True + +class Counter(CheckedInt): cxx_type = 'Counter'; size = 64; unsigned = True +class Tick(CheckedInt): cxx_type = 'Tick'; size = 64; unsigned = True +class TcpPort(CheckedInt): cxx_type = 'uint16_t'; size = 16; unsigned = True +class UdpPort(CheckedInt): cxx_type = 'uint16_t'; size = 16; unsigned = True + +class Percent(CheckedInt): cxx_type = 'int'; min = 0; max = 100 + +class Float(ParamValue, float): + pass + +class MemorySize(CheckedInt): + cxx_type = 'uint64_t' + size = 64 + unsigned = True + def __init__(self, value): + if isinstance(value, MemorySize): + self.value = value.value + else: + self.value = toMemorySize(value) + self._check() + +class MemorySize32(CheckedInt): + size = 32 + unsigned = True + def __init__(self, value): + if isinstance(value, MemorySize): + self.value = value.value + else: + self.value = toMemorySize(value) + self._check() + +class Addr(CheckedInt): + cxx_type = 'Addr' + cxx_predecls = ['#include "targetarch/isa_traits.hh"'] + size = 64 + unsigned = True + def __init__(self, value): + if isinstance(value, Addr): + self.value = value.value + else: + try: + self.value = toMemorySize(value) + except TypeError: + self.value = long(value) + self._check() + + +class MetaRange(type): + def __init__(cls, name, bases, dict): + super(MetaRange, cls).__init__(name, bases, dict) + if name == 'Range': + return + cls.cxx_type = 'Range< %s >' % cls.type.cxx_type + cls.cxx_predecls = \ + ['#include "base/range.hh"'] + cls.type.cxx_predecls + +class Range(ParamValue): + __metaclass__ = MetaRange + type = Int # default; can be overridden in subclasses + def __init__(self, *args, **kwargs): + def handle_kwargs(self, kwargs): + if 'end' in kwargs: + self.second = self.type(kwargs.pop('end')) + elif 'size' in kwargs: + self.second = self.first + self.type(kwargs.pop('size')) - 1 + else: + raise TypeError, "Either end or size must be specified" + + if len(args) == 0: + self.first = self.type(kwargs.pop('start')) + handle_kwargs(self, kwargs) + + elif len(args) == 1: + if kwargs: + self.first = self.type(args[0]) + handle_kwargs(self, kwargs) + elif isinstance(args[0], Range): + self.first = self.type(args[0].first) + self.second = self.type(args[0].second) + else: + self.first = self.type(0) + self.second = self.type(args[0]) - 1 + + elif len(args) == 2: + self.first = self.type(args[0]) + self.second = self.type(args[1]) + else: + raise TypeError, "Too many arguments specified" + + if kwargs: + raise TypeError, "too many keywords: %s" % kwargs.keys() + + def __str__(self): + return '%s:%s' % (self.first, self.second) + +class AddrRange(Range): + type = Addr + +class TickRange(Range): + type = Tick + +# Boolean parameter type. Python doesn't let you subclass bool, since +# it doesn't want to let you create multiple instances of True and +# False. Thus this is a little more complicated than String. +class Bool(ParamValue): + cxx_type = 'bool' + def __init__(self, value): + try: + self.value = toBool(value) + except TypeError: + self.value = bool(value) + + def __str__(self): + return str(self.value) + + def ini_str(self): + if self.value: + return 'true' + return 'false' + +def IncEthernetAddr(addr, val = 1): + bytes = map(lambda x: int(x, 16), addr.split(':')) + bytes[5] += val + for i in (5, 4, 3, 2, 1): + val,rem = divmod(bytes[i], 256) + bytes[i] = rem + if val == 0: + break + bytes[i - 1] += val + assert(bytes[0] <= 255) + return ':'.join(map(lambda x: '%02x' % x, bytes)) + +class NextEthernetAddr(object): + addr = "00:90:00:00:00:01" + + def __init__(self, inc = 1): + self.value = NextEthernetAddr.addr + NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc) + +class EthernetAddr(ParamValue): + cxx_type = 'Net::EthAddr' + cxx_predecls = ['#include "base/inet.hh"'] + swig_predecls = ['class Net::EthAddr;'] + def __init__(self, value): + if value == NextEthernetAddr: + self.value = value + return + + if not isinstance(value, str): + raise TypeError, "expected an ethernet address and didn't get one" + + bytes = value.split(':') + if len(bytes) != 6: + raise TypeError, 'invalid ethernet address %s' % value + + for byte in bytes: + if not 0 <= int(byte) <= 256: + raise TypeError, 'invalid ethernet address %s' % value + + self.value = value + + def unproxy(self, base): + if self.value == NextEthernetAddr: + self.addr = self.value().value + return self + + def __str__(self): + if self.value == NextEthernetAddr: + if hasattr(self, 'addr'): + return self.addr + else: + return "NextEthernetAddr (unresolved)" + else: + return self.value + +# Enumerated types are a little more complex. The user specifies the +# type as Enum(foo) where foo is either a list or dictionary of +# alternatives (typically strings, but not necessarily so). (In the +# long run, the integer value of the parameter will be the list index +# or the corresponding dictionary value. For now, since we only check +# that the alternative is valid and then spit it into a .ini file, +# there's not much point in using the dictionary.) + +# What Enum() must do is generate a new type encapsulating the +# provided list/dictionary so that specific values of the parameter +# can be instances of that type. We define two hidden internal +# classes (_ListEnum and _DictEnum) to serve as base classes, then +# derive the new type from the appropriate base class on the fly. + + +# Metaclass for Enum types +class MetaEnum(type): + def __init__(cls, name, bases, init_dict): + if init_dict.has_key('map'): + if not isinstance(cls.map, dict): + raise TypeError, "Enum-derived class attribute 'map' " \ + "must be of type dict" + # build list of value strings from map + cls.vals = cls.map.keys() + cls.vals.sort() + elif init_dict.has_key('vals'): + if not isinstance(cls.vals, list): + raise TypeError, "Enum-derived class attribute 'vals' " \ + "must be of type list" + # build string->value map from vals sequence + cls.map = {} + for idx,val in enumerate(cls.vals): + cls.map[val] = idx + else: + raise TypeError, "Enum-derived class must define "\ + "attribute 'map' or 'vals'" + + cls.cxx_type = name + '::Enum' + + super(MetaEnum, cls).__init__(name, bases, init_dict) + + # Generate C++ class declaration for this enum type. + # Note that we wrap the enum in a class/struct to act as a namespace, + # so that the enum strings can be brief w/o worrying about collisions. + def cxx_decl(cls): + s = 'struct %s {\n enum Enum {\n ' % cls.__name__ + s += ',\n '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals]) + s += '\n };\n};\n' + return s + +# Base class for enum types. +class Enum(ParamValue): + __metaclass__ = MetaEnum + vals = [] + + def __init__(self, value): + if value not in self.map: + raise TypeError, "Enum param got bad value '%s' (not in %s)" \ + % (value, self.vals) + self.value = value + + def __str__(self): + return self.value + +ticks_per_sec = None + +# how big does a rounding error need to be before we warn about it? +frequency_tolerance = 0.001 # 0.1% + +# convert a floting-point # of ticks to integer, and warn if rounding +# discards too much precision +def tick_check(float_ticks): + if float_ticks == 0: + return 0 + int_ticks = int(round(float_ticks)) + err = (float_ticks - int_ticks) / float_ticks + if err > frequency_tolerance: + print >> sys.stderr, "Warning: rounding error > tolerance" + print >> sys.stderr, " %f rounded to %d" % (float_ticks, int_ticks) + #raise ValueError + return int_ticks + +def getLatency(value): + if isinstance(value, Latency) or isinstance(value, Clock): + return value.value + elif isinstance(value, Frequency) or isinstance(value, RootClock): + return 1 / value.value + elif isinstance(value, str): + try: + return toLatency(value) + except ValueError: + try: + return 1 / toFrequency(value) + except ValueError: + pass # fall through + raise ValueError, "Invalid Frequency/Latency value '%s'" % value + + +class Latency(NumericParamValue): + cxx_type = 'Tick' + cxx_predecls = ['#include "sim/host.hh"'] + swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + + '%import "sim/host.hh"'] + def __init__(self, value): + self.value = getLatency(value) + + def __getattr__(self, attr): + if attr in ('latency', 'period'): + return self + if attr == 'frequency': + return Frequency(self) + raise AttributeError, "Latency object has no attribute '%s'" % attr + + # convert latency to ticks + def ini_str(self): + return str(tick_check(self.value * ticks_per_sec)) + +class Frequency(NumericParamValue): + cxx_type = 'Tick' + cxx_predecls = ['#include "sim/host.hh"'] + swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + + '%import "sim/host.hh"'] + def __init__(self, value): + self.value = 1 / getLatency(value) + + def __getattr__(self, attr): + if attr == 'frequency': + return self + if attr in ('latency', 'period'): + return Latency(self) + raise AttributeError, "Frequency object has no attribute '%s'" % attr + + # convert frequency to ticks per period + def ini_str(self): + return self.period.ini_str() + +# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz). +# We can't inherit from Frequency because we don't want it to be directly +# assignable to a regular Frequency parameter. +class RootClock(ParamValue): + cxx_type = 'Tick' + cxx_predecls = ['#include "sim/host.hh"'] + swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + + '%import "sim/host.hh"'] + def __init__(self, value): + self.value = 1 / getLatency(value) + + def __getattr__(self, attr): + if attr == 'frequency': + return Frequency(self) + if attr in ('latency', 'period'): + return Latency(self) + raise AttributeError, "Frequency object has no attribute '%s'" % attr + + def ini_str(self): + return str(tick_check(self.value)) + +# A generic frequency and/or Latency value. Value is stored as a latency, +# but to avoid ambiguity this object does not support numeric ops (* or /). +# An explicit conversion to a Latency or Frequency must be made first. +class Clock(ParamValue): + cxx_type = 'Tick' + cxx_predecls = ['#include "sim/host.hh"'] + swig_predecls = ['%import "python/m5/swig/stdint.i"\n' + + '%import "sim/host.hh"'] + def __init__(self, value): + self.value = getLatency(value) + + def __getattr__(self, attr): + if attr == 'frequency': + return Frequency(self) + if attr in ('latency', 'period'): + return Latency(self) + raise AttributeError, "Frequency object has no attribute '%s'" % attr + + def ini_str(self): + return self.period.ini_str() + +class NetworkBandwidth(float,ParamValue): + cxx_type = 'float' + def __new__(cls, value): + val = toNetworkBandwidth(value) / 8.0 + return super(cls, NetworkBandwidth).__new__(cls, val) + + def __str__(self): + return str(self.val) + + def ini_str(self): + return '%f' % (ticks_per_sec / float(self)) + +class MemoryBandwidth(float,ParamValue): + cxx_type = 'float' + def __new__(self, value): + val = toMemoryBandwidth(value) + return super(cls, MemoryBandwidth).__new__(cls, val) + + def __str__(self): + return str(self.val) + + def ini_str(self): + return '%f' % (ticks_per_sec / float(self)) + +# +# "Constants"... handy aliases for various values. +# + +# Some memory range specifications use this as a default upper bound. +MaxAddr = Addr.max +MaxTick = Tick.max +AllMemory = AddrRange(0, MaxAddr) + + +##################################################################### +# +# Port objects +# +# Ports are used to interconnect objects in the memory system. +# +##################################################################### + +# Port reference: encapsulates a reference to a particular port on a +# particular SimObject. +class PortRef(object): + def __init__(self, simobj, name, isVec): + assert(isSimObject(simobj)) + self.simobj = simobj + self.name = name + self.index = -1 + self.isVec = isVec # is this a vector port? + self.peer = None # not associated with another port yet + self.ccConnected = False # C++ port connection done? + + # Set peer port reference. Called via __setattr__ as a result of + # a port assignment, e.g., "obj1.port1 = obj2.port2". + def setPeer(self, other): + if self.isVec: + curMap = self.simobj._port_map.get(self.name, []) + self.index = len(curMap) + curMap.append(other) + else: + curMap = self.simobj._port_map.get(self.name) + if curMap and not self.isVec: + print "warning: overwriting port", self.simobj, self.name + curMap = other + self.simobj._port_map[self.name] = curMap + self.peer = other + + def clone(self, memo): + newRef = copy.copy(self) + assert(isSimObject(newRef.simobj)) + newRef.simobj = newRef.simobj(_memo=memo) + # Tricky: if I'm the *second* PortRef in the pair to be + # cloned, then my peer is still in the middle of its clone + # method, and thus hasn't returned to its owner's + # SimObject.__init__ to get installed in _port_map. As a + # result I have no way of finding the *new* peer object. So I + # mark myself as "waiting" for my peer, and I let the *first* + # PortRef clone call set up both peer pointers after I return. + newPeer = newRef.simobj._port_map.get(self.name) + if newPeer: + if self.isVec: + assert(self.index != -1) + newPeer = newPeer[self.index] + # other guy is all set up except for his peer pointer + assert(newPeer.peer == -1) # peer must be waiting for handshake + newPeer.peer = newRef + newRef.peer = newPeer + else: + # other guy is in clone; just wait for him to do the work + newRef.peer = -1 # mark as waiting for handshake + return newRef + + # Call C++ to create corresponding port connection between C++ objects + def ccConnect(self): + if self.ccConnected: # already done this + return + peer = self.peer + cc_main.connectPorts(self.simobj.getCCObject(), self.name, self.index, + peer.simobj.getCCObject(), peer.name, peer.index) + self.ccConnected = True + peer.ccConnected = True + +# Port description object. Like a ParamDesc object, this represents a +# logical port in the SimObject class, not a particular port on a +# SimObject instance. The latter are represented by PortRef objects. +class Port(object): + def __init__(self, desc): + self.desc = desc + self.isVec = False + + # Generate a PortRef for this port on the given SimObject with the + # given name + def makeRef(self, simobj, name): + return PortRef(simobj, name, self.isVec) + + # Connect an instance of this port (on the given SimObject with + # the given name) with the port described by the supplied PortRef + def connect(self, simobj, name, ref): + if not isinstance(ref, PortRef): + raise TypeError, \ + "assigning non-port reference port '%s'" % name + myRef = self.makeRef(simobj, name) + myRef.setPeer(ref) + ref.setPeer(myRef) + +# VectorPort description object. Like Port, but represents a vector +# of connections (e.g., as on a Bus). +class VectorPort(Port): + def __init__(self, desc): + Port.__init__(self, desc) + self.isVec = True + + +__all__ = ['Param', 'VectorParam', + 'Enum', 'Bool', 'String', 'Float', + 'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16', + 'Int32', 'UInt32', 'Int64', 'UInt64', + 'Counter', 'Addr', 'Tick', 'Percent', + 'TcpPort', 'UdpPort', 'EthernetAddr', + 'MemorySize', 'MemorySize32', + 'Latency', 'Frequency', 'RootClock', 'Clock', + 'NetworkBandwidth', 'MemoryBandwidth', + 'Range', 'AddrRange', 'TickRange', + 'MaxAddr', 'MaxTick', 'AllMemory', + 'Null', 'NULL', + 'NextEthernetAddr', + 'Port', 'VectorPort'] + +# see comment on imports at end of __init__.py. +from SimObject import SimObject, isSimObject, isSimObjectSequence, \ + isNullPointer +import proxy diff --git a/src/python/m5/proxy.py b/src/python/m5/proxy.py new file mode 100644 index 000000000..5be50481c --- /dev/null +++ b/src/python/m5/proxy.py @@ -0,0 +1,193 @@ +# Copyright (c) 2004-2006 The Regents of The University of Michigan +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Steve Reinhardt +# Nathan Binkert + +##################################################################### +# +# Proxy object support. +# +##################################################################### + +class BaseProxy(object): + def __init__(self, search_self, search_up): + self._search_self = search_self + self._search_up = search_up + self._multiplier = None + + def __setattr__(self, attr, value): + if not attr.startswith('_'): + raise AttributeError, 'cannot set attribute on proxy object' + super(BaseProxy, self).__setattr__(attr, value) + + # support multiplying proxies by constants + def __mul__(self, other): + if not isinstance(other, (int, long, float)): + raise TypeError, "Proxy multiplier must be integer" + if self._multiplier == None: + self._multiplier = other + else: + # support chained multipliers + self._multiplier *= other + return self + + __rmul__ = __mul__ + + def _mulcheck(self, result): + if self._multiplier == None: + return result + return result * self._multiplier + + def unproxy(self, base): + obj = base + done = False + + if self._search_self: + result, done = self.find(obj) + + if self._search_up: + while not done: + obj = obj._parent + if not obj: + break + result, done = self.find(obj) + + if not done: + raise AttributeError, \ + "Can't resolve proxy '%s' of type '%s' from '%s'" % \ + (self.path(), self._pdesc.ptype_str, base.path()) + + if isinstance(result, BaseProxy): + if result == self: + raise RuntimeError, "Cycle in unproxy" + result = result.unproxy(obj) + + return self._mulcheck(result) + + def getindex(obj, index): + if index == None: + return obj + try: + obj = obj[index] + except TypeError: + if index != 0: + raise + # if index is 0 and item is not subscriptable, just + # use item itself (so cpu[0] works on uniprocessors) + return obj + getindex = staticmethod(getindex) + + def set_param_desc(self, pdesc): + self._pdesc = pdesc + +class AttrProxy(BaseProxy): + def __init__(self, search_self, search_up, attr): + super(AttrProxy, self).__init__(search_self, search_up) + self._attr = attr + self._modifiers = [] + + def __getattr__(self, attr): + # python uses __bases__ internally for inheritance + if attr.startswith('_'): + return super(AttrProxy, self).__getattr__(self, attr) + if hasattr(self, '_pdesc'): + raise AttributeError, "Attribute reference on bound proxy" + self._modifiers.append(attr) + return self + + # support indexing on proxies (e.g., Self.cpu[0]) + def __getitem__(self, key): + if not isinstance(key, int): + raise TypeError, "Proxy object requires integer index" + self._modifiers.append(key) + return self + + def find(self, obj): + try: + val = getattr(obj, self._attr) + except: + return None, False + while isproxy(val): + val = val.unproxy(obj) + for m in self._modifiers: + if isinstance(m, str): + val = getattr(val, m) + elif isinstance(m, int): + val = val[m] + else: + assert("Item must be string or integer") + while isproxy(val): + val = val.unproxy(obj) + return val, True + + def path(self): + p = self._attr + for m in self._modifiers: + if isinstance(m, str): + p += '.%s' % m + elif isinstance(m, int): + p += '[%d]' % m + else: + assert("Item must be string or integer") + return p + +class AnyProxy(BaseProxy): + def find(self, obj): + return obj.find_any(self._pdesc.ptype) + + def path(self): + return 'any' + +def isproxy(obj): + if isinstance(obj, (BaseProxy, params.EthernetAddr)): + return True + elif isinstance(obj, (list, tuple)): + for v in obj: + if isproxy(v): + return True + return False + +class ProxyFactory(object): + def __init__(self, search_self, search_up): + self.search_self = search_self + self.search_up = search_up + + def __getattr__(self, attr): + if attr == 'any': + return AnyProxy(self.search_self, self.search_up) + else: + return AttrProxy(self.search_self, self.search_up, attr) + +# global objects for handling proxies +Parent = ProxyFactory(search_self = False, search_up = True) +Self = ProxyFactory(search_self = True, search_up = False) + +# limit exports on 'from proxy import *' +__all__ = ['Parent', 'Self'] + +# see comment on imports at end of __init__.py. +import params # for EthernetAddr diff --git a/src/python/m5/util.py b/src/python/m5/util.py new file mode 100644 index 000000000..28b8b1b94 --- /dev/null +++ b/src/python/m5/util.py @@ -0,0 +1,59 @@ +# Copyright (c) 2004-2006 The Regents of The University of Michigan +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Steve Reinhardt +# Nathan Binkert + +############################# +# +# Utility classes & methods +# +############################# + +class Singleton(type): + def __call__(cls, *args, **kwargs): + if hasattr(cls, '_instance'): + return cls._instance + + cls._instance = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instance + +# Apply method to object. +# applyMethod(obj, 'meth', ) is equivalent to obj.meth() +def applyMethod(obj, meth, *args, **kwargs): + return getattr(obj, meth)(*args, **kwargs) + +# If the first argument is an (non-sequence) object, apply the named +# method with the given arguments. If the first argument is a +# sequence, apply the method to each element of the sequence (a la +# 'map'). +def applyOrMap(objOrSeq, meth, *args, **kwargs): + if not isinstance(objOrSeq, (list, tuple)): + return applyMethod(objOrSeq, meth, *args, **kwargs) + else: + return [applyMethod(o, meth, *args, **kwargs) for o in objOrSeq] + +