params.py revision 4167
1848SN/A# Copyright (c) 2004-2006 The Regents of The University of Michigan
29956SN/A# All rights reserved.
39956SN/A#
49956SN/A# Redistribution and use in source and binary forms, with or without
59956SN/A# modification, are permitted provided that the following conditions are
69956SN/A# met: redistributions of source code must retain the above copyright
79956SN/A# notice, this list of conditions and the following disclaimer;
89956SN/A# redistributions in binary form must reproduce the above copyright
99956SN/A# notice, this list of conditions and the following disclaimer in the
109956SN/A# documentation and/or other materials provided with the distribution;
119956SN/A# neither the name of the copyright holders nor the names of its
129956SN/A# contributors may be used to endorse or promote products derived from
139956SN/A# this software without specific prior written permission.
141762SN/A#
15848SN/A# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16848SN/A# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17848SN/A# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18848SN/A# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19848SN/A# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20848SN/A# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21848SN/A# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22848SN/A# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23848SN/A# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24848SN/A# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25848SN/A# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26848SN/A#
27848SN/A# Authors: Steve Reinhardt
28848SN/A#          Nathan Binkert
29848SN/A
30848SN/A#####################################################################
31848SN/A#
32848SN/A# Parameter description classes
33848SN/A#
34848SN/A# The _params dictionary in each class maps parameter names to either
35848SN/A# a Param or a VectorParam object.  These objects contain the
36848SN/A# parameter description string, the parameter type, and the default
37848SN/A# value (if any).  The convert() method on these objects is used to
38848SN/A# force whatever value is assigned to the parameter to the appropriate
392665SN/A# type.
402665SN/A#
412665SN/A# Note that the default values are loaded into the class's attribute
42848SN/A# space when the parameter dictionary is initialized (in
43848SN/A# MetaSimObject._new_param()); after that point they aren't used.
44848SN/A#
45848SN/A#####################################################################
46848SN/A
47848SN/Aimport copy
4811264Sandreas.sandberg@arm.comimport datetime
4911264Sandreas.sandberg@arm.comimport inspect
50848SN/Aimport sys
51848SN/Aimport time
52848SN/A
53848SN/Aimport convert
54848SN/Aimport ticks
554762SN/Afrom util import *
562565SN/A
57848SN/A# Dummy base class to identify types that are legitimate for SimObject
58848SN/A# parameters.
598229SN/Aclass ParamValue(object):
608232SN/A
6111264Sandreas.sandberg@arm.com    cxx_predecls = []
6211264Sandreas.sandberg@arm.com    swig_predecls = []
634762SN/A
64848SN/A    # default for printing to .ini file is regular string conversion.
65848SN/A    # will be overridden in some cases
66848SN/A    def ini_str(self):
672107SN/A        return str(self)
68848SN/A
695034SN/A    # allows us to blithely call unproxy() on things without checking
705034SN/A    # if they're really proxies or not
7112087Sspwilson2@wisc.edu    def unproxy(self, base):
7212087Sspwilson2@wisc.edu        return self
7312087Sspwilson2@wisc.edu
7412087Sspwilson2@wisc.edu# Regular parameter description.
7512087Sspwilson2@wisc.educlass ParamDesc(object):
7612087Sspwilson2@wisc.edu    def __init__(self, ptype_str, ptype, *args, **kwargs):
7712087Sspwilson2@wisc.edu        self.ptype_str = ptype_str
7812087Sspwilson2@wisc.edu        # remember ptype only if it is provided
79848SN/A        if ptype != None:
80893SN/A            self.ptype = ptype
815034SN/A
82893SN/A        if args:
83849SN/A            if len(args) == 1:
841722SN/A                self.desc = args[0]
85849SN/A            elif len(args) == 2:
86849SN/A                self.default = args[0]
87849SN/A                self.desc = args[1]
88849SN/A            else:
89849SN/A                raise TypeError, 'too many arguments'
90849SN/A
91849SN/A        if kwargs.has_key('desc'):
92849SN/A            assert(not hasattr(self, 'desc'))
93849SN/A            self.desc = kwargs['desc']
94849SN/A            del kwargs['desc']
95849SN/A
96849SN/A        if kwargs.has_key('default'):
97849SN/A            assert(not hasattr(self, 'default'))
98849SN/A            self.default = kwargs['default']
9911101SN/A            del kwargs['default']
10011101SN/A
101849SN/A        if kwargs:
102849SN/A            raise TypeError, 'extra unknown kwargs %s' % kwargs
103849SN/A
104849SN/A        if not hasattr(self, 'desc'):
105849SN/A            raise TypeError, 'desc attribute missing'
106849SN/A
107849SN/A    def __getattr__(self, attr):
108849SN/A        if attr == 'ptype':
109849SN/A            try:
110849SN/A                ptype = eval(self.ptype_str, objects.__dict__)
111849SN/A                if not isinstance(ptype, type):
112849SN/A                    raise NameError
1131853SN/A                self.ptype = ptype
1141853SN/A                return ptype
115849SN/A            except NameError:
1161722SN/A                raise TypeError, \
117849SN/A                      "Param qualifier '%s' is not a type" % self.ptype_str
1181722SN/A        raise AttributeError, "'%s' object has no attribute '%s'" % \
119849SN/A              (type(self).__name__, attr)
1201722SN/A
121849SN/A    def convert(self, value):
1221722SN/A        if isinstance(value, proxy.BaseProxy):
1231722SN/A            value.set_param_desc(self)
1241722SN/A            return value
125849SN/A        if not hasattr(self, 'ptype') and isNullPointer(value):
1261722SN/A            # deferred evaluation of SimObject; continue to defer if
1271722SN/A            # we're just assigning a null pointer
128849SN/A            return value
1291722SN/A        if isinstance(value, self.ptype):
130849SN/A            return value
1312989SN/A        if isNullPointer(value) and isSimObjectClass(self.ptype):
132849SN/A            return value
1331722SN/A        return self.ptype(value)
134849SN/A
1351886SN/A    def cxx_predecls(self):
136849SN/A        return self.ptype.cxx_predecls
1371722SN/A
1381817SN/A    def swig_predecls(self):
1391817SN/A        return self.ptype.swig_predecls
1401817SN/A
141893SN/A    def cxx_decl(self):
142893SN/A        return '%s %s;' % (self.ptype.cxx_type, self.name)
143893SN/A
144893SN/A# Vector-valued parameter description.  Just like ParamDesc, except
145893SN/A# that the value is a vector (list) of the specified type instead of a
146893SN/A# single value.
147893SN/A
148893SN/Aclass VectorParamValue(list):
149893SN/A    def ini_str(self):
150893SN/A        return ' '.join([v.ini_str() for v in self])
151893SN/A
152893SN/A    def unproxy(self, base):
153893SN/A        return [v.unproxy(base) for v in self]
154893SN/A
155893SN/Aclass SimObjVector(VectorParamValue):
156893SN/A    def print_ini(self):
157893SN/A        for v in self:
158893SN/A            v.print_ini()
159893SN/A
160893SN/Aclass VectorParamDesc(ParamDesc):
161893SN/A    # Convert assigned value to appropriate type.  If the RHS is not a
162893SN/A    # list or tuple, it generates a single-element list.
163893SN/A    def convert(self, value):
164893SN/A        if isinstance(value, (list, tuple)):
165893SN/A            # list: coerce each element into new list
1669956SN/A            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
167849SN/A            if isSimObjectSequence(tmp_list):
168849SN/A                return SimObjVector(tmp_list)
169849SN/A            else:
170849SN/A                return VectorParamValue(tmp_list)
171849SN/A        else:
172849SN/A            # singleton: leave it be (could coerce to a single-element
173849SN/A            # list here, but for some historical reason we don't...
174849SN/A            return ParamDesc.convert(self, value)
175849SN/A
176849SN/A    def cxx_predecls(self):
177849SN/A        return ['#include <vector>'] + self.ptype.cxx_predecls
178849SN/A
179849SN/A    def swig_predecls(self):
180849SN/A        return ['%include "std_vector.i"'] + self.ptype.swig_predecls
181849SN/A
182893SN/A    def cxx_decl(self):
1831817SN/A        return 'std::vector< %s > %s;' % (self.ptype.cxx_type, self.name)
1841817SN/A
1851817SN/Aclass ParamFactory(object):
1861817SN/A    def __init__(self, param_desc_class, ptype_str = None):
187849SN/A        self.param_desc_class = param_desc_class
188849SN/A        self.ptype_str = ptype_str
189864SN/A
190864SN/A    def __getattr__(self, attr):
191864SN/A        if self.ptype_str:
192864SN/A            attr = self.ptype_str + '.' + attr
193929SN/A        return ParamFactory(self.param_desc_class, attr)
194929SN/A
195929SN/A    # E.g., Param.Int(5, "number of widgets")
196929SN/A    def __call__(self, *args, **kwargs):
197929SN/A        caller_frame = inspect.currentframe().f_back
198929SN/A        ptype = None
199861SN/A        try:
200864SN/A            ptype = eval(self.ptype_str,
201861SN/A                         caller_frame.f_globals, caller_frame.f_locals)
202861SN/A            if not isinstance(ptype, type):
2035772SN/A                raise TypeError, \
204861SN/A                      "Param qualifier is not a type: %s" % ptype
205861SN/A        except NameError:
206861SN/A            # if name isn't defined yet, assume it's a SimObject, and
207861SN/A            # try to resolve it later
208849SN/A            pass
209849SN/A        return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs)
210849SN/A
211849SN/AParam = ParamFactory(ParamDesc)
212849SN/AVectorParam = ParamFactory(VectorParamDesc)
2135772SN/A
214849SN/A#####################################################################
2155772SN/A#
2165772SN/A# Parameter Types
2175772SN/A#
2185772SN/A# Though native Python types could be used to specify parameter types
2195772SN/A# (the 'ptype' field of the Param and VectorParam classes), it's more
2205772SN/A# flexible to define our own set of types.  This gives us more control
2215772SN/A# over how Python expressions are converted to values (via the
2225772SN/A# __init__() constructor) and how these values are printed out (via
2235772SN/A# the __str__() conversion method).
2241817SN/A#
2255772SN/A#####################################################################
2265772SN/A
2275772SN/A# String-valued parameter.  Just mixin the ParamValue class with the
2285772SN/A# built-in str class.
2295772SN/Aclass String(ParamValue,str):
2305772SN/A    cxx_type = 'std::string'
2315772SN/A    cxx_predecls = ['#include <string>']
2321817SN/A    swig_predecls = ['%include "std_string.i"\n' +
2335772SN/A                     '%apply const std::string& {std::string *};']
2345772SN/A    pass
2355772SN/A
2365772SN/A# superclass for "numeric" parameter values, to emulate math
2375772SN/A# operations in a type-safe way.  e.g., a Latency times an int returns
2385772SN/A# a new Latency object.
2395772SN/Aclass NumericParamValue(ParamValue):
2405772SN/A    def __str__(self):
2415772SN/A        return str(self.value)
2425772SN/A
2435772SN/A    def __float__(self):
2445772SN/A        return float(self.value)
2455772SN/A
2465772SN/A    def __long__(self):
2475772SN/A        return long(self.value)
2485772SN/A
2495772SN/A    def __int__(self):
2505772SN/A        return int(self.value)
2511817SN/A
2521817SN/A    # hook for bounds checking
2535772SN/A    def _check(self):
254849SN/A        return
2555772SN/A
256849SN/A    def __mul__(self, other):
257849SN/A        newobj = self.__class__(self)
258849SN/A        newobj.value *= other
2595772SN/A        newobj._check()
260849SN/A        return newobj
2615772SN/A
2625772SN/A    __rmul__ = __mul__
2635772SN/A
2645772SN/A    def __div__(self, other):
2655772SN/A        newobj = self.__class__(self)
2665772SN/A        newobj.value /= other
267849SN/A        newobj._check()
2685772SN/A        return newobj
2695772SN/A
2705772SN/A    def __sub__(self, other):
2715772SN/A        newobj = self.__class__(self)
2725772SN/A        newobj.value -= other
2735772SN/A        newobj._check()
2745772SN/A        return newobj
2755772SN/A
2765772SN/A# Metaclass for bounds-checked integer parameters.  See CheckedInt.
2775772SN/Aclass CheckedIntType(type):
2785772SN/A    def __init__(cls, name, bases, dict):
2795772SN/A        super(CheckedIntType, cls).__init__(name, bases, dict)
2801817SN/A
2815772SN/A        # CheckedInt is an abstract base class, so we actually don't
2825772SN/A        # want to do any processing on it... the rest of this code is
2835772SN/A        # just for classes that derive from CheckedInt.
2845772SN/A        if name == 'CheckedInt':
2855772SN/A            return
2865772SN/A
2875772SN/A        if not cls.cxx_predecls:
2881817SN/A            # most derived types require this, so we just do it here once
2895772SN/A            cls.cxx_predecls = ['#include "sim/host.hh"']
2905772SN/A
2915772SN/A        if not cls.swig_predecls:
2925772SN/A            # most derived types require this, so we just do it here once
2935772SN/A            cls.swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
2945772SN/A                                 '%import "sim/host.hh"']
2955772SN/A
2965772SN/A        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
2975772SN/A            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
2985772SN/A                panic("CheckedInt subclass %s must define either\n" \
2995772SN/A                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
3005772SN/A                      % name);
3015772SN/A            if cls.unsigned:
3025772SN/A                cls.min = 0
3035772SN/A                cls.max = 2 ** cls.size - 1
3045772SN/A            else:
3055772SN/A                cls.min = -(2 ** (cls.size - 1))
3065772SN/A                cls.max = (2 ** (cls.size - 1)) - 1
3075772SN/A
3081817SN/A# Abstract superclass for bounds-checked integer parameters.  This
3091817SN/A# class is subclassed to generate parameter classes with specific
3105772SN/A# bounds.  Initialization of the min and max bounds is done in the
311849SN/A# metaclass CheckedIntType.__init__.
3125772SN/Aclass CheckedInt(NumericParamValue):
3135772SN/A    __metaclass__ = CheckedIntType
3145772SN/A
3155772SN/A    def _check(self):
3165772SN/A        if not self.min <= self.value <= self.max:
3175772SN/A            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
3185772SN/A                  (self.min, self.value, self.max)
3195772SN/A
3205772SN/A    def __init__(self, value):
3215772SN/A        if isinstance(value, str):
3225772SN/A            self.value = convert.toInteger(value)
3235772SN/A        elif isinstance(value, (int, long, float, NumericParamValue)):
3245772SN/A            self.value = long(value)
3255772SN/A        else:
3265772SN/A            raise TypeError, "Can't convert object of type %s to CheckedInt" \
3275772SN/A                  % type(value).__name__
3285772SN/A        self._check()
3295772SN/A
3305772SN/Aclass Int(CheckedInt):      cxx_type = 'int';      size = 32; unsigned = False
331849SN/Aclass Unsigned(CheckedInt): cxx_type = 'unsigned'; size = 32; unsigned = True
3322566SN/A
3332566SN/Aclass Int8(CheckedInt):     cxx_type =   'int8_t'; size =  8; unsigned = False
334849SN/Aclass UInt8(CheckedInt):    cxx_type =  'uint8_t'; size =  8; unsigned = True
335849SN/Aclass Int16(CheckedInt):    cxx_type =  'int16_t'; size = 16; unsigned = False
336849SN/Aclass UInt16(CheckedInt):   cxx_type = 'uint16_t'; size = 16; unsigned = True
337849SN/Aclass Int32(CheckedInt):    cxx_type =  'int32_t'; size = 32; unsigned = False
338849SN/Aclass UInt32(CheckedInt):   cxx_type = 'uint32_t'; size = 32; unsigned = True
339849SN/Aclass Int64(CheckedInt):    cxx_type =  'int64_t'; size = 64; unsigned = False
340849SN/Aclass UInt64(CheckedInt):   cxx_type = 'uint64_t'; size = 64; unsigned = True
341849SN/A
342849SN/Aclass Counter(CheckedInt):  cxx_type = 'Counter';  size = 64; unsigned = True
3439956SN/Aclass Tick(CheckedInt):     cxx_type = 'Tick';     size = 64; unsigned = True
3449956SN/Aclass TcpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
3459956SN/Aclass UdpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
3469956SN/A
3479956SN/Aclass Percent(CheckedInt):  cxx_type = 'int'; min = 0; max = 100
3489956SN/A
349849SN/Aclass Float(ParamValue, float):
350849SN/A    pass
351849SN/A
352849SN/Aclass MemorySize(CheckedInt):
35310913SN/A    cxx_type = 'uint64_t'
3547823SN/A    size = 64
3552565SN/A    unsigned = True
3562565SN/A    def __init__(self, value):
3572565SN/A        if isinstance(value, MemorySize):
3582565SN/A            self.value = value.value
359849SN/A        else:
360849SN/A            self.value = convert.toMemorySize(value)
361849SN/A        self._check()
362849SN/A
363849SN/Aclass MemorySize32(CheckedInt):
3649956SN/A    size = 32
3659956SN/A    unsigned = True
3669956SN/A    def __init__(self, value):
3679956SN/A        if isinstance(value, MemorySize):
3689956SN/A            self.value = value.value
3698522SN/A        else:
3701625SN/A            self.value = convert.toMemorySize(value)
3711625SN/A        self._check()
372896SN/A
373896SN/Aclass Addr(CheckedInt):
374896SN/A    cxx_type = 'Addr'
375896SN/A    cxx_predecls = ['#include "targetarch/isa_traits.hh"']
376989SN/A    size = 64
377989SN/A    unsigned = True
378849SN/A    def __init__(self, value):
379849SN/A        if isinstance(value, Addr):
3802565SN/A            self.value = value.value
381849SN/A        else:
3822565SN/A            try:
383849SN/A                self.value = convert.toMemorySize(value)
384849SN/A            except TypeError:
385849SN/A                self.value = long(value)
3862565SN/A        self._check()
387849SN/A    def __add__(self, other):
3881763SN/A        if isinstance(other, Addr):
389864SN/A            return self.value + other.value
390849SN/A        else:
3911634SN/A            return self.value + other
3921634SN/A
393849SN/A
3947823SN/Aclass MetaRange(type):
3952565SN/A    def __init__(cls, name, bases, dict):
396864SN/A        super(MetaRange, cls).__init__(name, bases, dict)
3972627SN/A        if name == 'Range':
3982627SN/A            return
3992627SN/A        cls.cxx_type = 'Range< %s >' % cls.type.cxx_type
40011522Sstephan.diestelhorst@arm.com        cls.cxx_predecls = \
40111522Sstephan.diestelhorst@arm.com                       ['#include "base/range.hh"'] + cls.type.cxx_predecls
4022627SN/A
4032627SN/Aclass Range(ParamValue):
4042627SN/A    __metaclass__ = MetaRange
4052627SN/A    type = Int # default; can be overridden in subclasses
4062627SN/A    def __init__(self, *args, **kwargs):
4072627SN/A        def handle_kwargs(self, kwargs):
4082627SN/A            if 'end' in kwargs:
4092627SN/A                self.second = self.type(kwargs.pop('end'))
4102627SN/A            elif 'size' in kwargs:
4112627SN/A                self.second = self.first + self.type(kwargs.pop('size')) - 1
4122627SN/A            else:
4132627SN/A                raise TypeError, "Either end or size must be specified"
4142627SN/A
4152627SN/A        if len(args) == 0:
4162627SN/A            self.first = self.type(kwargs.pop('start'))
4172627SN/A            handle_kwargs(self, kwargs)
4182627SN/A
4192627SN/A        elif len(args) == 1:
4202627SN/A            if kwargs:
4212627SN/A                self.first = self.type(args[0])
4222627SN/A                handle_kwargs(self, kwargs)
4232627SN/A            elif isinstance(args[0], Range):
4242627SN/A                self.first = self.type(args[0].first)
4252627SN/A                self.second = self.type(args[0].second)
4262627SN/A            else:
4272627SN/A                self.first = self.type(0)
4282627SN/A                self.second = self.type(args[0]) - 1
429864SN/A
4302565SN/A        elif len(args) == 2:
4312565SN/A            self.first = self.type(args[0])
4322565SN/A            self.second = self.type(args[1])
4339956SN/A        else:
4349956SN/A            raise TypeError, "Too many arguments specified"
4359956SN/A
4369956SN/A        if kwargs:
4379956SN/A            raise TypeError, "too many keywords: %s" % kwargs.keys()
4389956SN/A
4399956SN/A    def __str__(self):
4409956SN/A        return '%s:%s' % (self.first, self.second)
441864SN/A
4422565SN/Aclass AddrRange(Range):
4432565SN/A    type = Addr
4442565SN/A
4452565SN/Aclass TickRange(Range):
4462565SN/A    type = Tick
4472565SN/A
4482565SN/A# Boolean parameter type.  Python doesn't let you subclass bool, since
44910913SN/A# it doesn't want to let you create multiple instances of True and
4507823SN/A# False.  Thus this is a little more complicated than String.
4512565SN/Aclass Bool(ParamValue):
4522565SN/A    cxx_type = 'bool'
4532565SN/A    def __init__(self, value):
4542565SN/A        try:
4552565SN/A            self.value = convert.toBool(value)
4562627SN/A        except TypeError:
4572627SN/A            self.value = bool(value)
4582627SN/A
4592627SN/A    def __str__(self):
4602565SN/A        return str(self.value)
461849SN/A
4622565SN/A    def ini_str(self):
4632565SN/A        if self.value:
4642565SN/A            return 'true'
4652565SN/A        return 'false'
466849SN/A
467849SN/Adef IncEthernetAddr(addr, val = 1):
468849SN/A    bytes = map(lambda x: int(x, 16), addr.split(':'))
469849SN/A    bytes[5] += val
470849SN/A    for i in (5, 4, 3, 2, 1):
471849SN/A        val,rem = divmod(bytes[i], 256)
4722565SN/A        bytes[i] = rem
473861SN/A        if val == 0:
474861SN/A            break
4752565SN/A        bytes[i - 1] += val
476864SN/A    assert(bytes[0] <= 255)
477861SN/A    return ':'.join(map(lambda x: '%02x' % x, bytes))
4782565SN/A
479861SN/Aclass NextEthernetAddr(object):
480864SN/A    addr = "00:90:00:00:00:01"
481861SN/A
482849SN/A    def __init__(self, inc = 1):
483920SN/A        self.value = NextEthernetAddr.addr
484849SN/A        NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc)
485849SN/A
486849SN/Aclass EthernetAddr(ParamValue):
487849SN/A    cxx_type = 'Net::EthAddr'
488849SN/A    cxx_predecls = ['#include "base/inet.hh"']
489849SN/A    swig_predecls = ['class Net::EthAddr;']
490849SN/A    def __init__(self, value):
491849SN/A        if value == NextEthernetAddr:
492849SN/A            self.value = value
4932565SN/A            return
494849SN/A
4951763SN/A        if not isinstance(value, str):
496864SN/A            raise TypeError, "expected an ethernet address and didn't get one"
4972565SN/A
498849SN/A        bytes = value.split(':')
4991634SN/A        if len(bytes) != 6:
5001634SN/A            raise TypeError, 'invalid ethernet address %s' % value
5011634SN/A
5022565SN/A        for byte in bytes:
5032565SN/A            if not 0 <= int(byte) <= 256:
5042565SN/A                raise TypeError, 'invalid ethernet address %s' % value
5052565SN/A
5062565SN/A        self.value = value
5072565SN/A
5082565SN/A    def unproxy(self, base):
5098522SN/A        if self.value == NextEthernetAddr:
5108522SN/A            self.addr = self.value().value
511849SN/A        return self
5127823SN/A
5132565SN/A    def __str__(self):
514864SN/A        if self.value == NextEthernetAddr:
5152565SN/A            if hasattr(self, 'addr'):
5162565SN/A                return self.addr
5172565SN/A            else:
5189956SN/A                return "NextEthernetAddr (unresolved)"
5199956SN/A        else:
5209956SN/A            return self.value
5219956SN/A
5229956SN/Atime_formats = [ "%a %b %d %H:%M:%S %Z %Y",
5239956SN/A                 "%a %b %d %H:%M:%S %Z %Y",
5249956SN/A                 "%Y/%m/%d %H:%M:%S",
5259956SN/A                 "%Y/%m/%d %H:%M",
5262565SN/A                 "%Y/%m/%d",
5272565SN/A                 "%m/%d/%Y %H:%M:%S",
5282565SN/A                 "%m/%d/%Y %H:%M",
5292565SN/A                 "%m/%d/%Y",
5302565SN/A                 "%m/%d/%y %H:%M:%S",
53110913SN/A                 "%m/%d/%y %H:%M",
5327823SN/A                 "%m/%d/%y"]
5338522SN/A
5342565SN/A
5352565SN/Adef parse_time(value):
5362565SN/A    from time import gmtime, strptime, struct_time, time
5372565SN/A    from datetime import datetime, date
5382565SN/A
5398522SN/A    if isinstance(value, struct_time):
5408522SN/A        return value
5412627SN/A
5422627SN/A    if isinstance(value, (int, long)):
5432627SN/A        return gmtime(value)
5442627SN/A
5452565SN/A    if isinstance(value, (datetime, date)):
546849SN/A        return value.timetuple()
5478522SN/A
5488522SN/A    if isinstance(value, str):
5492565SN/A        if value in ('Now', 'Today'):
5502565SN/A            return time.gmtime(time.time())
5512565SN/A
5522565SN/A        for format in time_formats:
553849SN/A            try:
554849SN/A                return strptime(value, format)
555849SN/A            except ValueError:
556849SN/A                pass
557849SN/A
558849SN/A    raise ValueError, "Could not parse '%s' as a time" % value
5598522SN/A
5608522SN/Aclass Time(ParamValue):
561849SN/A    cxx_type = 'time_t'
562849SN/A    def __init__(self, value):
563849SN/A        self.value = parse_time(value)
564849SN/A
565849SN/A    def __str__(self):
566849SN/A        tm = self.value
567849SN/A        return ' '.join([ str(tm[i]) for i in xrange(8)])
568849SN/A
569849SN/A    def ini_str(self):
570849SN/A        return str(self)
571849SN/A
572849SN/A# Enumerated types are a little more complex.  The user specifies the
573849SN/A# type as Enum(foo) where foo is either a list or dictionary of
574849SN/A# alternatives (typically strings, but not necessarily so).  (In the
575849SN/A# long run, the integer value of the parameter will be the list index
576849SN/A# or the corresponding dictionary value.  For now, since we only check
577849SN/A# that the alternative is valid and then spit it into a .ini file,
578849SN/A# there's not much point in using the dictionary.)
579849SN/A
580849SN/A# What Enum() must do is generate a new type encapsulating the
581849SN/A# provided list/dictionary so that specific values of the parameter
582849SN/A# can be instances of that type.  We define two hidden internal
583849SN/A# classes (_ListEnum and _DictEnum) to serve as base classes, then
584849SN/A# derive the new type from the appropriate base class on the fly.
585849SN/A
586849SN/A
587849SN/A# Metaclass for Enum types
588849SN/Aclass MetaEnum(type):
589849SN/A    def __init__(cls, name, bases, init_dict):
590849SN/A        if init_dict.has_key('map'):
591849SN/A            if not isinstance(cls.map, dict):
592849SN/A                raise TypeError, "Enum-derived class attribute 'map' " \
593849SN/A                      "must be of type dict"
594849SN/A            # build list of value strings from map
595849SN/A            cls.vals = cls.map.keys()
596849SN/A            cls.vals.sort()
597849SN/A        elif init_dict.has_key('vals'):
598849SN/A            if not isinstance(cls.vals, list):
599849SN/A                raise TypeError, "Enum-derived class attribute 'vals' " \
600849SN/A                      "must be of type list"
601849SN/A            # build string->value map from vals sequence
602849SN/A            cls.map = {}
603849SN/A            for idx,val in enumerate(cls.vals):
604849SN/A                cls.map[val] = idx
605849SN/A        else:
606849SN/A            raise TypeError, "Enum-derived class must define "\
607849SN/A                  "attribute 'map' or 'vals'"
608896SN/A
609896SN/A        cls.cxx_type = name + '::Enum'
610849SN/A
611849SN/A        super(MetaEnum, cls).__init__(name, bases, init_dict)
612849SN/A
613849SN/A    # Generate C++ class declaration for this enum type.
6147823SN/A    # Note that we wrap the enum in a class/struct to act as a namespace,
615849SN/A    # so that the enum strings can be brief w/o worrying about collisions.
616849SN/A    def cxx_decl(cls):
617849SN/A        s = 'struct %s {\n  enum Enum {\n    ' % cls.__name__
618849SN/A        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
619849SN/A        s += '\n  };\n};\n'
620849SN/A        return s
6211625SN/A
622849SN/A# Base class for enum types.
623849SN/Aclass Enum(ParamValue):
6241625SN/A    __metaclass__ = MetaEnum
625849SN/A    vals = []
626849SN/A
627849SN/A    def __init__(self, value):
628849SN/A        if value not in self.map:
629849SN/A            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
630849SN/A                  % (value, self.vals)
631849SN/A        self.value = value
632849SN/A
633849SN/A    def __str__(self):
634849SN/A        return self.value
635849SN/A
636849SN/A# how big does a rounding error need to be before we warn about it?
637849SN/Afrequency_tolerance = 0.001  # 0.1%
638849SN/A
6391722SN/Aclass TickParamValue(NumericParamValue):
6409533SN/A    cxx_type = 'Tick'
641849SN/A    cxx_predecls = ['#include "sim/host.hh"']
642849SN/A    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
643849SN/A                     '%import "sim/host.hh"']
644849SN/A
645849SN/Aclass Latency(TickParamValue):
646849SN/A    def __init__(self, value):
647849SN/A        if isinstance(value, (Latency, Clock)):
648849SN/A            self.ticks = value.ticks
649849SN/A            self.value = value.value
6501722SN/A        elif isinstance(value, Frequency):
6511722SN/A            self.ticks = value.ticks
6521722SN/A            self.value = 1.0 / value.value
6531722SN/A        elif value.endswith('t'):
6541722SN/A            self.ticks = True
6551722SN/A            self.value = int(value[:-1])
6561722SN/A        else:
6571722SN/A            self.ticks = False
65810587SN/A            self.value = convert.toLatency(value)
659849SN/A
660849SN/A    def __getattr__(self, attr):
661849SN/A        if attr in ('latency', 'period'):
662849SN/A            return self
663849SN/A        if attr == 'frequency':
6641722SN/A            return Frequency(self)
66511980Sjason@lowepower.com        raise AttributeError, "Latency object has no attribute '%s'" % attr
6661722SN/A
667849SN/A    # convert latency to ticks
668849SN/A    def ini_str(self):
669849SN/A        if self.ticks or self.value == 0:
670849SN/A            return '%d' % self.value
6711722SN/A        else:
6721722SN/A            return '%d' % (ticks.fromSeconds(self.value))
673849SN/A
674849SN/Aclass Frequency(TickParamValue):
675849SN/A    def __init__(self, value):
676849SN/A        if isinstance(value, (Latency, Clock)):
677893SN/A            if value.value == 0:
678849SN/A                self.value = 0
679893SN/A            else:
680849SN/A                self.value = 1.0 / value.value
681849SN/A            self.ticks = value.ticks
682849SN/A        elif isinstance(value, Frequency):
683877SN/A            self.value = value.value
684849SN/A            self.ticks = value.ticks
685849SN/A        else:
686849SN/A            self.ticks = False
687849SN/A            self.value = convert.toFrequency(value)
688849SN/A
6891722SN/A    def __getattr__(self, attr):
6901722SN/A        if attr == 'frequency':
691849SN/A            return self
692849SN/A        if attr in ('latency', 'period'):
693849SN/A            return Latency(self)
694849SN/A        raise AttributeError, "Frequency object has no attribute '%s'" % attr
695893SN/A
696849SN/A    # convert latency to ticks
697893SN/A    def ini_str(self):
6988522SN/A        if self.ticks or self.value == 0:
699849SN/A            return '%d' % self.value
700849SN/A        else:
701849SN/A            return '%d' % (ticks.fromSeconds(1.0 / self.value))
702849SN/A
703849SN/A# A generic frequency and/or Latency value.  Value is stored as a latency,
704849SN/A# but to avoid ambiguity this object does not support numeric ops (* or /).
705849SN/A# An explicit conversion to a Latency or Frequency must be made first.
7061722SN/Aclass Clock(ParamValue):
707849SN/A    cxx_type = 'Tick'
70812392Sjason@lowepower.com    cxx_predecls = ['#include "sim/host.hh"']
7091722SN/A    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
710849SN/A                     '%import "sim/host.hh"']
711849SN/A    def __init__(self, value):
712849SN/A        if isinstance(value, (Latency, Clock)):
713849SN/A            self.ticks = value.ticks
714893SN/A            self.value = value.value
715849SN/A        elif isinstance(value, Frequency):
716893SN/A            self.ticks = value.ticks
7178522SN/A            self.value = 1.0 / value.value
718849SN/A        elif value.endswith('t'):
719849SN/A            self.ticks = True
720849SN/A            self.value = int(value[:-1])
721849SN/A        else:
722849SN/A            self.ticks = False
723849SN/A            self.value = convert.anyToLatency(value)
724849SN/A
725849SN/A    def __getattr__(self, attr):
726849SN/A        if attr == 'frequency':
727849SN/A            return Frequency(self)
728849SN/A        if attr in ('latency', 'period'):
729849SN/A            return Latency(self)
730849SN/A        raise AttributeError, "Frequency object has no attribute '%s'" % attr
731893SN/A
732849SN/A    def ini_str(self):
733893SN/A        return self.period.ini_str()
734893SN/A
735893SN/Aclass NetworkBandwidth(float,ParamValue):
736849SN/A    cxx_type = 'float'
737849SN/A    def __new__(cls, value):
738849SN/A        # convert to bits per second
739849SN/A        val = convert.toNetworkBandwidth(value)
740849SN/A        return super(cls, NetworkBandwidth).__new__(cls, val)
741849SN/A
742849SN/A    def __str__(self):
743849SN/A        return str(self.val)
744849SN/A
745849SN/A    def ini_str(self):
746849SN/A        # convert to seconds per byte
747849SN/A        value = 8.0 / float(self)
7481625SN/A        # convert to ticks per byte
749849SN/A        return '%f' % (ticks.fromSeconds(value))
750849SN/A
751849SN/Aclass MemoryBandwidth(float,ParamValue):
752849SN/A    cxx_type = 'float'
753849SN/A    def __new__(self, value):
754849SN/A        # we want the number of ticks per byte of data
7551817SN/A        val = convert.toMemoryBandwidth(value)
756849SN/A        return super(cls, MemoryBandwidth).__new__(cls, val)
7571817SN/A
758849SN/A    def __str__(self):
759849SN/A        return str(self.val)
760849SN/A
761849SN/A    def ini_str(self):
762849SN/A        # convert to seconds per byte
7631625SN/A        value = 1.0 / float(self)
764849SN/A        # convert to ticks per byte
765849SN/A        return '%f' % (ticks.fromSeconds(value))
766849SN/A
767849SN/A#
768849SN/A# "Constants"... handy aliases for various values.
769849SN/A#
770849SN/A
771849SN/A# Special class for NULL pointers.  Note the special check in
772849SN/A# make_param_value() above that lets these be assigned where a
773849SN/A# SimObject is required.
774849SN/A# only one copy of a particular node
775849SN/Aclass NullSimObject(object):
776849SN/A    __metaclass__ = Singleton
777849SN/A
778849SN/A    def __call__(cls):
779849SN/A        return cls
780849SN/A
781849SN/A    def _instantiate(self, parent = None, path = ''):
782893SN/A        pass
783893SN/A
784893SN/A    def ini_str(self):
785893SN/A        return 'Null'
786893SN/A
787893SN/A    def unproxy(self, base):
788893SN/A        return self
789893SN/A
790893SN/A    def set_path(self, parent, name):
791893SN/A        pass
792893SN/A    def __str__(self):
793893SN/A        return 'Null'
794893SN/A
795849SN/A# The only instance you'll ever need...
796893SN/ANULL = NullSimObject()
797849SN/A
798893SN/Adef isNullPointer(value):
799849SN/A    return isinstance(value, NullSimObject)
800893SN/A
801849SN/A# Some memory range specifications use this as a default upper bound.
802849SN/AMaxAddr = Addr.max
803849SN/AMaxTick = Tick.max
804849SN/AAllMemory = AddrRange(0, MaxAddr)
805893SN/A
806849SN/A
807849SN/A#####################################################################
808849SN/A#
809849SN/A# Port objects
810849SN/A#
811849SN/A# Ports are used to interconnect objects in the memory system.
812849SN/A#
813849SN/A#####################################################################
814849SN/A
815849SN/A# Port reference: encapsulates a reference to a particular port on a
816849SN/A# particular SimObject.
817849SN/Aclass PortRef(object):
818849SN/A    def __init__(self, simobj, name):
819893SN/A        assert(isSimObject(simobj) or isSimObjectClass(simobj))
820849SN/A        self.simobj = simobj
821849SN/A        self.name = name
822849SN/A        self.peer = None   # not associated with another port yet
823849SN/A        self.ccConnected = False # C++ port connection done?
824849SN/A        self.index = -1  # always -1 for non-vector ports
825849SN/A
826849SN/A    def __str__(self):
827849SN/A        return '%s.%s' % (self.simobj, self.name)
828849SN/A
829849SN/A    # for config.ini, print peer's name (not ours)
830849SN/A    def ini_str(self):
831849SN/A        return str(self.peer)
832849SN/A
833849SN/A    def __getattr__(self, attr):
834849SN/A        if attr == 'peerObj':
835849SN/A            # shorthand for proxies
836849SN/A            return self.peer.simobj
837849SN/A        raise AttributeError, "'%s' object has no attribute '%s'" % \
838849SN/A              (self.__class__.__name__, attr)
839849SN/A
840849SN/A    # Full connection is symmetric (both ways).  Called via
841849SN/A    # SimObject.__setattr__ as a result of a port assignment, e.g.,
842849SN/A    # "obj1.portA = obj2.portB", or via VectorPortElementRef.__setitem__,
843849SN/A    # e.g., "obj1.portA[3] = obj2.portB".
844849SN/A    def connect(self, other):
845849SN/A        if isinstance(other, VectorPortRef):
846849SN/A            # reference to plain VectorPort is implicit append
847849SN/A            other = other._get_next()
848849SN/A        if self.peer and not proxy.isproxy(self.peer):
849849SN/A            print "warning: overwriting port", self, \
850849SN/A                  "value", self.peer, "with", other
851849SN/A        self.peer = other
852849SN/A        if proxy.isproxy(other):
853849SN/A            other.set_param_desc(PortParamDesc())
854849SN/A        elif isinstance(other, PortRef):
855849SN/A            if other.peer is not self:
856849SN/A                other.connect(self)
857893SN/A        else:
858849SN/A            raise TypeError, \
859893SN/A                  "assigning non-port reference '%s' to port '%s'" \
860849SN/A                  % (other, self)
861877SN/A
86211980Sjason@lowepower.com    def clone(self, simobj, memo):
86311980Sjason@lowepower.com        if memo.has_key(self):
864877SN/A            return memo[self]
8651722SN/A        newRef = copy.copy(self)
866877SN/A        memo[self] = newRef
867877SN/A        newRef.simobj = simobj
8681722SN/A        assert(isSimObject(newRef.simobj))
869877SN/A        if self.peer and not proxy.isproxy(self.peer):
870877SN/A            peerObj = self.peer.simobj(_memo=memo)
871877SN/A            newRef.peer = self.peer.clone(peerObj, memo)
872877SN/A            assert(not isinstance(newRef.peer, VectorPortRef))
873877SN/A        return newRef
874877SN/A
875877SN/A    def unproxy(self, simobj):
876849SN/A        assert(simobj is self.simobj)
8771817SN/A        if proxy.isproxy(self.peer):
878857SN/A            try:
879857SN/A                realPeer = self.peer.unproxy(self.simobj)
880849SN/A            except:
881849SN/A                print "Error in unproxying port '%s' of %s" % \
882849SN/A                      (self.name, self.simobj.path())
883849SN/A                raise
884849SN/A            self.connect(realPeer)
885849SN/A
886849SN/A    # Call C++ to create corresponding port connection between C++ objects
887849SN/A    def ccConnect(self):
888849SN/A        if self.ccConnected: # already done this
889849SN/A            return
890849SN/A        peer = self.peer
891849SN/A        internal.sim_object.connectPorts(self.simobj.getCCObject(), self.name,
892849SN/A            self.index, peer.simobj.getCCObject(), peer.name, peer.index)
893849SN/A        self.ccConnected = True
894849SN/A        peer.ccConnected = True
895849SN/A
896849SN/A# A reference to an individual element of a VectorPort... much like a
897849SN/A# PortRef, but has an index.
898849SN/Aclass VectorPortElementRef(PortRef):
899849SN/A    def __init__(self, simobj, name, index):
900849SN/A        PortRef.__init__(self, simobj, name)
901849SN/A        self.index = index
902849SN/A
903849SN/A    def __str__(self):
904849SN/A        return '%s.%s[%d]' % (self.simobj, self.name, self.index)
905877SN/A
9061817SN/A# A reference to a complete vector-valued port (not just a single element).
907877SN/A# Can be indexed to retrieve individual VectorPortElementRef instances.
908877SN/Aclass VectorPortRef(object):
909849SN/A    def __init__(self, simobj, name):
910849SN/A        assert(isSimObject(simobj) or isSimObjectClass(simobj))
911849SN/A        self.simobj = simobj
912849SN/A        self.name = name
913849SN/A        self.elements = []
914849SN/A
915849SN/A    def __str__(self):
916849SN/A        return '%s.%s[:]' % (self.simobj, self.name)
917849SN/A
918877SN/A    # for config.ini, print peer's name (not ours)
919893SN/A    def ini_str(self):
920877SN/A        return ' '.join([el.ini_str() for el in self.elements])
921893SN/A
922877SN/A    def __getitem__(self, key):
923877SN/A        if not isinstance(key, int):
924877SN/A            raise TypeError, "VectorPort index must be integer"
925877SN/A        if key >= len(self.elements):
926849SN/A            # need to extend list
927849SN/A            ext = [VectorPortElementRef(self.simobj, self.name, i)
928849SN/A                   for i in range(len(self.elements), key+1)]
929849SN/A            self.elements.extend(ext)
930849SN/A        return self.elements[key]
931849SN/A
932849SN/A    def _get_next(self):
933849SN/A        return self[len(self.elements)]
934849SN/A
935849SN/A    def __setitem__(self, key, value):
936849SN/A        if not isinstance(key, int):
937849SN/A            raise TypeError, "VectorPort index must be integer"
938849SN/A        self[key].connect(value)
939849SN/A
940849SN/A    def connect(self, other):
941849SN/A        if isinstance(other, (list, tuple)):
942893SN/A            # Assign list of port refs to vector port.
943849SN/A            # For now, append them... not sure if that's the right semantics
944893SN/A            # or if it should replace the current vector.
945849SN/A            for ref in other:
946893SN/A                self._get_next().connect(ref)
947849SN/A        else:
948849SN/A            # scalar assignment to plain VectorPort is implicit append
949849SN/A            self._get_next().connect(other)
950849SN/A
951893SN/A    def clone(self, simobj, memo):
952893SN/A        if memo.has_key(self):
953893SN/A            return memo[self]
954893SN/A        newRef = copy.copy(self)
955893SN/A        memo[self] = newRef
956893SN/A        newRef.simobj = simobj
957849SN/A        assert(isSimObject(newRef.simobj))
958849SN/A        newRef.elements = [el.clone(simobj, memo) for el in self.elements]
959849SN/A        return newRef
960849SN/A
961849SN/A    def unproxy(self, simobj):
962849SN/A        [el.unproxy(simobj) for el in self.elements]
963849SN/A
964849SN/A    def ccConnect(self):
965849SN/A        [el.ccConnect() for el in self.elements]
966849SN/A
967849SN/A# Port description object.  Like a ParamDesc object, this represents a
968849SN/A# logical port in the SimObject class, not a particular port on a
969849SN/A# SimObject instance.  The latter are represented by PortRef objects.
970849SN/Aclass Port(object):
971857SN/A    # Port("description") or Port(default, "description")
972857SN/A    def __init__(self, *args):
973857SN/A        if len(args) == 1:
974849SN/A            self.desc = args[0]
975849SN/A        elif len(args) == 2:
976849SN/A            self.default = args[0]
977849SN/A            self.desc = args[1]
978849SN/A        else:
9791817SN/A            raise TypeError, 'wrong number of arguments'
980849SN/A        # self.name is set by SimObject class on assignment
981849SN/A        # e.g., pio_port = Port("blah") sets self.name to 'pio_port'
982849SN/A
983849SN/A    # Generate a PortRef for this port on the given SimObject with the
984849SN/A    # given name
985849SN/A    def makeRef(self, simobj):
986849SN/A        return PortRef(simobj, self.name)
987849SN/A
988849SN/A    # Connect an instance of this port (on the given SimObject with
989849SN/A    # the given name) with the port described by the supplied PortRef
990849SN/A    def connect(self, simobj, ref):
991893SN/A        self.makeRef(simobj).connect(ref)
992893SN/A
993893SN/A# VectorPort description object.  Like Port, but represents a vector
994849SN/A# of connections (e.g., as on a Bus).
995893SN/Aclass VectorPort(Port):
996849SN/A    def __init__(self, *args):
997849SN/A        Port.__init__(self, *args)
998878SN/A        self.isVec = True
999878SN/A
1000878SN/A    def makeRef(self, simobj):
1001878SN/A        return VectorPortRef(simobj, self.name)
1002849SN/A
1003849SN/A# 'Fake' ParamDesc for Port references to assign to the _pdesc slot of
1004849SN/A# proxy objects (via set_param_desc()) so that proxy error messages
1005849SN/A# make sense.
1006849SN/Aclass PortParamDesc(object):
1007849SN/A    __metaclass__ = Singleton
1008849SN/A
1009849SN/A    ptype_str = 'Port'
1010849SN/A    ptype = Port
1011849SN/A
1012849SN/A
1013849SN/A__all__ = ['Param', 'VectorParam',
1014849SN/A           'Enum', 'Bool', 'String', 'Float',
1015849SN/A           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1016849SN/A           'Int32', 'UInt32', 'Int64', 'UInt64',
1017849SN/A           'Counter', 'Addr', 'Tick', 'Percent',
1018849SN/A           'TcpPort', 'UdpPort', 'EthernetAddr',
1019893SN/A           'MemorySize', 'MemorySize32',
1020849SN/A           'Latency', 'Frequency', 'Clock',
1021893SN/A           'NetworkBandwidth', 'MemoryBandwidth',
1022849SN/A           'Range', 'AddrRange', 'TickRange',
1023849SN/A           'MaxAddr', 'MaxTick', 'AllMemory',
1024849SN/A           'Time',
1025849SN/A           'NextEthernetAddr', 'NULL',
1026849SN/A           'Port', 'VectorPort']
1027849SN/A
1028849SN/A# see comment on imports at end of __init__.py.
1029849SN/Afrom SimObject import isSimObject, isSimObjectSequence, isSimObjectClass
1030849SN/Aimport proxy
1031849SN/Aimport objects
1032849SN/Aimport internal
1033849SN/A