config: Add hooks to enable new config sys

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.
This commit is contained in:
Geoffrey Blake 2014-08-10 05:39:13 -04:00
parent 47313601c1
commit 09b5003815
2 changed files with 297 additions and 8 deletions

View 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()

View 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