config: Add hooks to enable new config sys
authorGeoffrey Blake <Geoffrey.Blake@arm.com>
Sun, 10 Aug 2014 09:39:13 +0000 (05:39 -0400)
committerGeoffrey Blake <Geoffrey.Blake@arm.com>
Sun, 10 Aug 2014 09:39:13 +0000 (05:39 -0400)
This patch adds helper functions to SimObject.py, params.py and
simulate.py to enable the new configuration system.  Functions like
enumerateParams() in SimObject lets the config system auto-generate
command line options for simobjects to be modified on the command
line.

Params in params.py have __call__() added
to their definition to allow the argparse module to use them
as a type to check command input is in the proper format.

src/python/m5/SimObject.py
src/python/m5/params.py

index 3784c716e141938f6bfb9f93b87825cb9408dfa0..81923ac7c6c647b9e68c6b3f26efff367b0e7e99 100644 (file)
@@ -172,6 +172,7 @@ class MetaSimObject(type):
 
         # class or instance attributes
         cls._values = multidict()   # param values
+        cls._hr_values = multidict() # human readable param values
         cls._children = multidict() # SimObject children
         cls._port_refs = multidict() # port ref objects
         cls._instantiated = False # really instantiated, cloned, or subclassed
@@ -197,6 +198,7 @@ class MetaSimObject(type):
             cls._params.parent = base._params
             cls._ports.parent = base._ports
             cls._values.parent = base._values
+            cls._hr_values.parent = base._hr_values
             cls._children.parent = base._children
             cls._port_refs.parent = base._port_refs
             # mark base as having been subclassed
@@ -273,6 +275,7 @@ class MetaSimObject(type):
     def _set_param(cls, name, value, param):
         assert(param.name == name)
         try:
+            hr_value = value
             value = param.convert(value)
         except Exception, e:
             msg = "%s\nError setting param %s.%s to %s\n" % \
@@ -284,6 +287,11 @@ class MetaSimObject(type):
         # it gets cloned properly when the class is instantiated
         if isSimObjectOrVector(value) and not value.has_parent():
             cls._add_cls_child(name, value)
+        # update human-readable values of the param if it has a literal
+        # value and is not an object or proxy.
+        if not (isSimObjectOrVector(value) or\
+                isinstance(value, m5.proxy.BaseProxy)):
+            cls._hr_values[name] = hr_value
 
     def _add_cls_child(cls, name, child):
         # It's a little funky to have a class as a parent, but these
@@ -585,6 +593,28 @@ struct PyObject;
 def isSimObjectOrVector(value):
     return False
 
+# This class holds information about each simobject parameter
+# that should be displayed on the command line for use in the
+# configuration system.
+class ParamInfo(object):
+  def __init__(self, type, desc, type_str, example, default_val, access_str):
+    self.type = type
+    self.desc = desc
+    self.type_str = type_str
+    self.example_str = example
+    self.default_val = default_val
+    # The string representation used to access this param through python.
+    # The method to access this parameter presented on the command line may
+    # be different, so this needs to be stored for later use.
+    self.access_str = access_str
+    self.created = True
+
+  # Make it so we can only set attributes at initialization time
+  # and effectively make this a const object.
+  def __setattr__(self, name, value):
+    if not "created" in self.__dict__:
+      self.__dict__[name] = value
+
 # 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).
@@ -621,6 +651,64 @@ class SimObject(object):
     void startup();
 ''')
 
+    # Returns a dict of all the option strings that can be
+    # generated as command line options for this simobject instance
+    # by tracing all reachable params in the top level instance and
+    # any children it contains.
+    def enumerateParams(self, flags_dict = {},
+                        cmd_line_str = "", access_str = ""):
+        if hasattr(self, "_paramEnumed"):
+            print "Cycle detected enumerating params"
+        else:
+            self._paramEnumed = True
+            # Scan the children first to pick up all the objects in this SimObj
+            for keys in self._children:
+                child = self._children[keys]
+                next_cmdline_str = cmd_line_str + keys
+                next_access_str = access_str + keys
+                if not isSimObjectVector(child):
+                    next_cmdline_str = next_cmdline_str + "."
+                    next_access_str = next_access_str + "."
+                flags_dict = child.enumerateParams(flags_dict,
+                                                   next_cmdline_str,
+                                                   next_access_str)
+
+            # Go through the simple params in the simobject in this level
+            # of the simobject hierarchy and save information about the
+            # parameter to be used for generating and processing command line
+            # options to the simulator to set these parameters.
+            for keys,values in self._params.items():
+                if values.isCmdLineSettable():
+                    type_str = ''
+                    ex_str = values.example_str()
+                    ptype = None
+                    if isinstance(values, VectorParamDesc):
+                        type_str = 'Vector_%s' % values.ptype_str
+                        ptype = values
+                    else:
+                        type_str = '%s' % values.ptype_str
+                        ptype = values.ptype
+
+                    if keys in self._hr_values\
+                       and keys in self._values\
+                       and not isinstance(self._values[keys], m5.proxy.BaseProxy):
+                        cmd_str = cmd_line_str + keys
+                        acc_str = access_str + keys
+                        flags_dict[cmd_str] = ParamInfo(ptype,
+                                    self._params[keys].desc, type_str, ex_str,
+                                    values.pretty_print(self._hr_values[keys]),
+                                    acc_str)
+                    elif not keys in self._hr_values\
+                         and not keys in self._values:
+                        # Empty param
+                        cmd_str = cmd_line_str + keys
+                        acc_str = access_str + keys
+                        flags_dict[cmd_str] = ParamInfo(ptype,
+                                    self._params[keys].desc,
+                                    type_str, ex_str, '', acc_str)
+
+        return flags_dict
+
     # 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
@@ -661,6 +749,7 @@ class SimObject(object):
         # individual value settings can be overridden but we still
         # inherit late changes to non-overridden class values.
         self._values = multidict(ancestor._values)
+        self._hr_values = multidict(ancestor._hr_values)
         # clone SimObject-valued parameters
         for key,val in ancestor._values.iteritems():
             val = tryAsSimObjectOrVector(val)
@@ -751,6 +840,7 @@ class SimObject(object):
         param = self._params.get(attr)
         if param:
             try:
+                hr_value = value
                 value = param.convert(value)
             except Exception, e:
                 msg = "%s\nError setting param %s.%s to %s\n" % \
@@ -761,6 +851,13 @@ class SimObject(object):
             # implicitly parent unparented objects assigned as params
             if isSimObjectOrVector(value) and not value.has_parent():
                 self.add_child(attr, value)
+            # set the human-readable value dict if this is a param
+            # with a literal value and is not being set as an object
+            # or proxy.
+            if not (isSimObjectOrVector(value) or\
+                    isinstance(value, m5.proxy.BaseProxy)):
+                self._hr_values[attr] = hr_value
+
             return
 
         # if RHS is a SimObject, it's an implicit child assignment
@@ -778,7 +875,13 @@ class SimObject(object):
     def __getitem__(self, key):
         if key == 0:
             return self
-        raise TypeError, "Non-zero index '%s' to SimObject" % key
+        raise IndexError, "Non-zero index '%s' to SimObject" % key
+
+    # this hack allows us to iterate over a SimObject that may
+    # not be a vector, so we can call a loop over it and get just one
+    # element.
+    def __len__(self):
+        return 1
 
     # Also implemented by SimObjectVector
     def clear_parent(self, old_parent):
@@ -1054,8 +1157,9 @@ class SimObject(object):
             # Cycles in the configuration hierarchy are not supported. This
             # will catch the resulting recursion and stop.
             self._ccObject = -1
-            params = self.getCCParams()
-            self._ccObject = params.create()
+            if not self.abstract:
+                params = self.getCCParams()
+                self._ccObject = params.create()
         elif self._ccObject == -1:
             raise RuntimeError, "%s: Cycle found in configuration hierarchy." \
                   % self.path()
index ad06e5309298ca83d6517461eeef91a565b7a37c..88d38f87ab4e170811fc2d8bb675c1c9f3bdea00 100644 (file)
@@ -93,7 +93,7 @@ class MetaParamValue(type):
 # parameters.
 class ParamValue(object):
     __metaclass__ = MetaParamValue
-
+    cmd_line_settable = False
 
     # Generate the code needed as a prerequisite for declaring a C++
     # object of this type.  Typically generates one or more #include
@@ -119,6 +119,10 @@ class ParamValue(object):
     def unproxy(self, base):
         return self
 
+    # Produce a human readable version of the stored value
+    def pretty_print(self, value):
+        return str(value)
+
 # Regular parameter description.
 class ParamDesc(object):
     def __init__(self, ptype_str, ptype, *args, **kwargs):
@@ -162,6 +166,19 @@ class ParamDesc(object):
         raise AttributeError, "'%s' object has no attribute '%s'" % \
               (type(self).__name__, attr)
 
+    def example_str(self):
+        if hasattr(self.ptype, "ex_str"):
+            return self.ptype.ex_str
+        else:
+            return self.ptype_str
+
+    # Is the param available to be exposed on the command line
+    def isCmdLineSettable(self):
+        if hasattr(self.ptype, "cmd_line_settable"):
+            return self.ptype.cmd_line_settable
+        else:
+            return False
+
     def convert(self, value):
         if isinstance(value, proxy.BaseProxy):
             value.set_param_desc(self)
@@ -176,6 +193,13 @@ class ParamDesc(object):
             return value
         return self.ptype(value)
 
+    def pretty_print(self, value):
+        if isinstance(value, proxy.BaseProxy):
+           return str(value)
+        if isNullPointer(value):
+           return NULL
+        return self.ptype(value).pretty_print(value)
+
     def cxx_predecls(self, code):
         code('#include <cstddef>')
         self.ptype.cxx_predecls(code)
@@ -260,6 +284,26 @@ class SimObjectVector(VectorParamValue):
         value.set_parent(val.get_parent(), val._name)
         super(SimObjectVector, self).__setitem__(key, value)
 
+    # Enumerate the params of each member of the SimObject vector. Creates
+    # strings that will allow indexing into the vector by the python code and
+    # allow it to be specified on the command line.
+    def enumerateParams(self, flags_dict = {},
+                        cmd_line_str = "",
+                        access_str = ""):
+        if hasattr(self, "_paramEnumed"):
+            print "Cycle detected enumerating params at %s?!" % (cmd_line_str)
+        else:
+            x = 0
+            for vals in self:
+                # Each entry in the SimObjectVector should be an
+                # instance of a SimObject
+                flags_dict = vals.enumerateParams(flags_dict,
+                                                  cmd_line_str + "%d." % x,
+                                                  access_str + "[%d]." % x)
+                x = x + 1
+
+        return flags_dict
+
 class VectorParamDesc(ParamDesc):
     # Convert assigned value to appropriate type.  If the RHS is not a
     # list or tuple, it generates a single-element list.
@@ -276,6 +320,39 @@ class VectorParamDesc(ParamDesc):
         else:
             return VectorParamValue(tmp_list)
 
+    # Produce a human readable example string that describes
+    # how to set this vector parameter in the absence of a default
+    # value.
+    def example_str(self):
+        s = super(VectorParamDesc, self).example_str()
+        help_str = "[" + s + "," + s + ", ...]"
+        return help_str
+
+    # Produce a human readable representation of the value of this vector param.
+    def pretty_print(self, value):
+        if isinstance(value, (list, tuple)):
+            tmp_list = [ ParamDesc.pretty_print(self, v) for v in value ]
+        elif isinstance(value, str):
+            tmp_list = [ ParamDesc.pretty_print(self, v) for v in value.split(',') ]
+        else:
+            tmp_list = [ ParamDesc.pretty_print(self, value) ]
+
+        return tmp_list
+
+    # This is a helper function for the new config system
+    def __call__(self, value):
+        if isinstance(value, (list, tuple)):
+            # list: coerce each element into new list
+            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
+        elif isinstance(value, str):
+            # If input is a csv string
+            tmp_list = [ ParamDesc.convert(self, v) for v in value.split(',') ]
+        else:
+            # singleton: coerce to a single-element list
+            tmp_list = [ ParamDesc.convert(self, value) ]
+
+        return VectorParamValue(tmp_list)
+
     def swig_module_name(self):
         return "%s_vector" % self.ptype_str
 
@@ -349,6 +426,7 @@ VectorParam = ParamFactory(VectorParamDesc)
 # built-in str class.
 class String(ParamValue,str):
     cxx_type = 'std::string'
+    cmd_line_settable = True
 
     @classmethod
     def cxx_predecls(self, code):
@@ -358,6 +436,10 @@ class String(ParamValue,str):
     def swig_predecls(cls, code):
         code('%include "std_string.i"')
 
+    def __call__(self, value):
+        self = value
+        return value
+
     def getValue(self):
         return self
 
@@ -430,6 +512,7 @@ class CheckedIntType(MetaParamValue):
 # metaclass CheckedIntType.__init__.
 class CheckedInt(NumericParamValue):
     __metaclass__ = CheckedIntType
+    cmd_line_settable = True
 
     def _check(self):
         if not self.min <= self.value <= self.max:
@@ -446,6 +529,10 @@ class CheckedInt(NumericParamValue):
                   % type(value).__name__
         self._check()
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     @classmethod
     def cxx_predecls(cls, code):
         # most derived types require this, so we just do it here once
@@ -490,19 +577,25 @@ class Cycles(CheckedInt):
 
 class Float(ParamValue, float):
     cxx_type = 'double'
+    cmdLineSettable = True
 
     def __init__(self, value):
-        if isinstance(value, (int, long, float, NumericParamValue, Float)):
+        if isinstance(value, (int, long, float, NumericParamValue, Float, str)):
             self.value = float(value)
         else:
             raise TypeError, "Can't convert object of type %s to Float" \
                   % type(value).__name__
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def getValue(self):
         return float(self.value)
 
 class MemorySize(CheckedInt):
     cxx_type = 'uint64_t'
+    ex_str = '512MB'
     size = 64
     unsigned = True
     def __init__(self, value):
@@ -514,6 +607,7 @@ class MemorySize(CheckedInt):
 
 class MemorySize32(CheckedInt):
     cxx_type = 'uint32_t'
+    ex_str = '512MB'
     size = 32
     unsigned = True
     def __init__(self, value):
@@ -541,6 +635,12 @@ class Addr(CheckedInt):
             return self.value + other.value
         else:
             return self.value + other
+    def pretty_print(self, value):
+        try:
+            val = convert.toMemorySize(value)
+        except TypeError:
+            val = long(value)
+        return "0x%x" % long(val)
 
 class AddrRange(ParamValue):
     cxx_type = 'AddrRange'
@@ -624,12 +724,18 @@ class AddrRange(ParamValue):
 # False.  Thus this is a little more complicated than String.
 class Bool(ParamValue):
     cxx_type = 'bool'
+    cmd_line_settable = True
+
     def __init__(self, value):
         try:
             self.value = convert.toBool(value)
         except TypeError:
             self.value = bool(value)
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def getValue(self):
         return bool(self.value)
 
@@ -668,6 +774,8 @@ def NextEthernetAddr():
 
 class EthernetAddr(ParamValue):
     cxx_type = 'Net::EthAddr'
+    ex_str = "00:90:00:00:00:01"
+    cmd_line_settable = True
 
     @classmethod
     def cxx_predecls(cls, code):
@@ -695,6 +803,10 @@ class EthernetAddr(ParamValue):
 
         self.value = value
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def unproxy(self, base):
         if self.value == NextEthernetAddr:
             return EthernetAddr(self.value())
@@ -711,6 +823,8 @@ class EthernetAddr(ParamValue):
 # the form "a.b.c.d", or an integer representing an IP.
 class IpAddress(ParamValue):
     cxx_type = 'Net::IpAddress'
+    ex_str = "127.0.0.1"
+    cmd_line_settable = True
 
     @classmethod
     def cxx_predecls(cls, code):
@@ -730,6 +844,10 @@ class IpAddress(ParamValue):
                 self.ip = long(value)
         self.verifyIp()
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def __str__(self):
         tup = [(self.ip >> i)  & 0xff for i in (24, 16, 8, 0)]
         return '%d.%d.%d.%d' % tuple(tup)
@@ -761,6 +879,8 @@ class IpAddress(ParamValue):
 # positional or keyword arguments.
 class IpNetmask(IpAddress):
     cxx_type = 'Net::IpNetmask'
+    ex_str = "127.0.0.0/24"
+    cmd_line_settable = True
 
     @classmethod
     def cxx_predecls(cls, code):
@@ -806,6 +926,10 @@ class IpNetmask(IpAddress):
 
         self.verify()
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def __str__(self):
         return "%s/%d" % (super(IpNetmask, self).__str__(), self.netmask)
 
@@ -833,6 +957,8 @@ class IpNetmask(IpAddress):
 # the form "a.b.c.d:p", or an ip and port as positional or keyword arguments.
 class IpWithPort(IpAddress):
     cxx_type = 'Net::IpWithPort'
+    ex_str = "127.0.0.1:80"
+    cmd_line_settable = True
 
     @classmethod
     def cxx_predecls(cls, code):
@@ -878,6 +1004,10 @@ class IpWithPort(IpAddress):
 
         self.verify()
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def __str__(self):
         return "%s:%d" % (super(IpWithPort, self).__str__(), self.port)
 
@@ -953,6 +1083,10 @@ class Time(ParamValue):
     def __init__(self, value):
         self.value = parse_time(value)
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def getValue(self):
         from m5.internal.params import tm
 
@@ -1111,6 +1245,7 @@ $wrapper $wrapper_name {
 class Enum(ParamValue):
     __metaclass__ = MetaEnum
     vals = []
+    cmd_line_settable = True
 
     # The name of the wrapping namespace or struct
     wrapper_name = 'Enums'
@@ -1127,6 +1262,10 @@ class Enum(ParamValue):
                   % (value, self.vals)
         self.value = value
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     @classmethod
     def cxx_predecls(cls, code):
         code('#include "enums/$0.hh"', cls.__name__)
@@ -1146,6 +1285,8 @@ frequency_tolerance = 0.001  # 0.1%
 
 class TickParamValue(NumericParamValue):
     cxx_type = 'Tick'
+    ex_str = "1MHz"
+    cmd_line_settable = True
 
     @classmethod
     def cxx_predecls(cls, code):
@@ -1156,10 +1297,16 @@ class TickParamValue(NumericParamValue):
         code('%import "stdint.i"')
         code('%import "base/types.hh"')
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def getValue(self):
         return long(self.value)
 
 class Latency(TickParamValue):
+    ex_str = "100ns"
+
     def __init__(self, value):
         if isinstance(value, (Latency, Clock)):
             self.ticks = value.ticks
@@ -1174,6 +1321,10 @@ class Latency(TickParamValue):
             self.ticks = False
             self.value = convert.toLatency(value)
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def __getattr__(self, attr):
         if attr in ('latency', 'period'):
             return self
@@ -1193,6 +1344,8 @@ class Latency(TickParamValue):
         return '%d' % self.getValue()
 
 class Frequency(TickParamValue):
+    ex_str = "1GHz"
+
     def __init__(self, value):
         if isinstance(value, (Latency, Clock)):
             if value.value == 0:
@@ -1207,6 +1360,10 @@ class Frequency(TickParamValue):
             self.ticks = False
             self.value = convert.toFrequency(value)
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
     def __getattr__(self, attr):
         if attr == 'frequency':
             return self
@@ -1242,6 +1399,13 @@ class Clock(TickParamValue):
             self.ticks = False
             self.value = convert.anyToLatency(value)
 
+    def __call__(self, value):
+        self.__init__(value)
+        return value
+
+    def __str__(self):
+        return "%s" % Latency(self)
+
     def __getattr__(self, attr):
         if attr == 'frequency':
             return Frequency(self)
@@ -1257,13 +1421,21 @@ class Clock(TickParamValue):
 
 class Voltage(float,ParamValue):
     cxx_type = 'double'
+    ex_str = "1V"
+    cmd_line_settable = False
+
     def __new__(cls, value):
         # convert to voltage
         val = convert.toVoltage(value)
         return super(cls, Voltage).__new__(cls, val)
 
+    def __call__(self, value):
+        val = convert.toVoltage(value)
+        self.__init__(val)
+        return value
+
     def __str__(self):
-        return str(self.val)
+        return str(self.getValue())
 
     def getValue(self):
         value = float(self)
@@ -1274,6 +1446,9 @@ class Voltage(float,ParamValue):
 
 class NetworkBandwidth(float,ParamValue):
     cxx_type = 'float'
+    ex_str = "1Gbps"
+    cmd_line_settable = True
+
     def __new__(cls, value):
         # convert to bits per second
         val = convert.toNetworkBandwidth(value)
@@ -1282,6 +1457,11 @@ class NetworkBandwidth(float,ParamValue):
     def __str__(self):
         return str(self.val)
 
+    def __call__(self, value):
+        val = convert.toNetworkBandwidth(value)
+        self.__init__(val)
+        return value
+
     def getValue(self):
         # convert to seconds per byte
         value = 8.0 / float(self)
@@ -1294,13 +1474,18 @@ class NetworkBandwidth(float,ParamValue):
 
 class MemoryBandwidth(float,ParamValue):
     cxx_type = 'float'
+    ex_str = "1GB/s"
+    cmd_line_settable = True
+
     def __new__(cls, value):
         # convert to bytes per second
         val = convert.toMemoryBandwidth(value)
         return super(cls, MemoryBandwidth).__new__(cls, val)
 
-    def __str__(self):
-        return str(self.val)
+    def __call__(self, value):
+        val = convert.toMemoryBandwidth(value)
+        self.__init__(val)
+        return value
 
     def getValue(self):
         # convert to seconds per byte