params.py revision 7673
1# Copyright (c) 2004-2006 The Regents of The University of Michigan
2# Copyright (c) 2010 Advanced Micro Devices, Inc.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met: redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer;
9# redistributions in binary form must reproduce the above copyright
10# notice, this list of conditions and the following disclaimer in the
11# documentation and/or other materials provided with the distribution;
12# neither the name of the copyright holders nor the names of its
13# contributors may be used to endorse or promote products derived from
14# this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27#
28# Authors: Steve Reinhardt
29#          Nathan Binkert
30
31#####################################################################
32#
33# Parameter description classes
34#
35# The _params dictionary in each class maps parameter names to either
36# a Param or a VectorParam object.  These objects contain the
37# parameter description string, the parameter type, and the default
38# value (if any).  The convert() method on these objects is used to
39# force whatever value is assigned to the parameter to the appropriate
40# type.
41#
42# Note that the default values are loaded into the class's attribute
43# space when the parameter dictionary is initialized (in
44# MetaSimObject._new_param()); after that point they aren't used.
45#
46#####################################################################
47
48import copy
49import datetime
50import re
51import sys
52import time
53import math
54
55import proxy
56import ticks
57from util import *
58
59def isSimObject(*args, **kwargs):
60    return SimObject.isSimObject(*args, **kwargs)
61
62def isSimObjectSequence(*args, **kwargs):
63    return SimObject.isSimObjectSequence(*args, **kwargs)
64
65def isSimObjectClass(*args, **kwargs):
66    return SimObject.isSimObjectClass(*args, **kwargs)
67
68allParams = {}
69
70class MetaParamValue(type):
71    def __new__(mcls, name, bases, dct):
72        cls = super(MetaParamValue, mcls).__new__(mcls, name, bases, dct)
73        assert name not in allParams
74        allParams[name] = cls
75        return cls
76
77
78# Dummy base class to identify types that are legitimate for SimObject
79# parameters.
80class ParamValue(object):
81    __metaclass__ = MetaParamValue
82
83    @classmethod
84    def cxx_predecls(cls, code):
85        pass
86
87    @classmethod
88    def swig_predecls(cls, code):
89        pass
90
91    # default for printing to .ini file is regular string conversion.
92    # will be overridden in some cases
93    def ini_str(self):
94        return str(self)
95
96    # allows us to blithely call unproxy() on things without checking
97    # if they're really proxies or not
98    def unproxy(self, base):
99        return self
100
101# Regular parameter description.
102class ParamDesc(object):
103    file_ext = 'ptype'
104
105    def __init__(self, ptype_str, ptype, *args, **kwargs):
106        self.ptype_str = ptype_str
107        # remember ptype only if it is provided
108        if ptype != None:
109            self.ptype = ptype
110
111        if args:
112            if len(args) == 1:
113                self.desc = args[0]
114            elif len(args) == 2:
115                self.default = args[0]
116                self.desc = args[1]
117            else:
118                raise TypeError, 'too many arguments'
119
120        if kwargs.has_key('desc'):
121            assert(not hasattr(self, 'desc'))
122            self.desc = kwargs['desc']
123            del kwargs['desc']
124
125        if kwargs.has_key('default'):
126            assert(not hasattr(self, 'default'))
127            self.default = kwargs['default']
128            del kwargs['default']
129
130        if kwargs:
131            raise TypeError, 'extra unknown kwargs %s' % kwargs
132
133        if not hasattr(self, 'desc'):
134            raise TypeError, 'desc attribute missing'
135
136    def __getattr__(self, attr):
137        if attr == 'ptype':
138            ptype = SimObject.allClasses[self.ptype_str]
139            assert isSimObjectClass(ptype)
140            self.ptype = ptype
141            return ptype
142
143        raise AttributeError, "'%s' object has no attribute '%s'" % \
144              (type(self).__name__, attr)
145
146    def convert(self, value):
147        if isinstance(value, proxy.BaseProxy):
148            value.set_param_desc(self)
149            return value
150        if not hasattr(self, 'ptype') and isNullPointer(value):
151            # deferred evaluation of SimObject; continue to defer if
152            # we're just assigning a null pointer
153            return value
154        if isinstance(value, self.ptype):
155            return value
156        if isNullPointer(value) and isSimObjectClass(self.ptype):
157            return value
158        return self.ptype(value)
159
160    def cxx_predecls(self, code):
161        self.ptype.cxx_predecls(code)
162
163    def swig_predecls(self, code):
164        self.ptype.swig_predecls(code)
165
166    def cxx_decl(self, code):
167        code('${{self.ptype.cxx_type}} ${{self.name}};')
168
169# Vector-valued parameter description.  Just like ParamDesc, except
170# that the value is a vector (list) of the specified type instead of a
171# single value.
172
173class VectorParamValue(list):
174    __metaclass__ = MetaParamValue
175    def __setattr__(self, attr, value):
176        raise AttributeError, \
177              "Not allowed to set %s on '%s'" % (attr, type(self).__name__)
178
179    def ini_str(self):
180        return ' '.join([v.ini_str() for v in self])
181
182    def getValue(self):
183        return [ v.getValue() for v in self ]
184
185    def unproxy(self, base):
186        return [v.unproxy(base) for v in self]
187
188class SimObjectVector(VectorParamValue):
189    # support clone operation
190    def __call__(self, **kwargs):
191        return SimObjectVector([v(**kwargs) for v in self])
192
193    def clear_parent(self, old_parent):
194        for v in self:
195            v.clear_parent(old_parent)
196
197    def set_parent(self, parent, name):
198        if len(self) == 1:
199            self[0].set_parent(parent, name)
200        else:
201            width = int(math.ceil(math.log(len(self))/math.log(10)))
202            for i,v in enumerate(self):
203                v.set_parent(parent, "%s%0*d" % (name, width, i))
204
205    def get_parent(self):
206        parent_set = set(v._parent for v in self)
207        if len(parent_set) != 1:
208            raise RuntimeError, \
209                  "SimObjectVector elements have inconsistent parent value."
210        return parent_set.pop()
211
212    # return 'cpu0 cpu1' etc. for print_ini()
213    def get_name(self):
214        return ' '.join([v._name for v in self])
215
216    # By iterating through the constituent members of the vector here
217    # we can nicely handle iterating over all a SimObject's children
218    # without having to provide lots of special functions on
219    # SimObjectVector directly.
220    def descendants(self):
221        for v in self:
222            for obj in v.descendants():
223                yield obj
224
225class VectorParamDesc(ParamDesc):
226    file_ext = 'vptype'
227
228    # Convert assigned value to appropriate type.  If the RHS is not a
229    # list or tuple, it generates a single-element list.
230    def convert(self, value):
231        if isinstance(value, (list, tuple)):
232            # list: coerce each element into new list
233            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
234        else:
235            # singleton: coerce to a single-element list
236            tmp_list = [ ParamDesc.convert(self, value) ]
237
238        if isSimObjectSequence(tmp_list):
239            return SimObjectVector(tmp_list)
240        else:
241            return VectorParamValue(tmp_list)
242
243    def swig_predecls(self, code):
244        code('%include "${{self.ptype_str}}_vptype.i"')
245
246    def swig_decl(self, code):
247        cxx_type = re.sub('std::', '', self.ptype.cxx_type)
248        code('%include "std_vector.i"')
249        self.ptype.swig_predecls(code)
250        code('''\
251namespace std {
252%template(vector_${{self.ptype_str}}) vector< $cxx_type >;
253}
254''')
255
256    def cxx_predecls(self, code):
257        code('#include <vector>')
258        self.ptype.cxx_predecls(code)
259
260    def cxx_decl(self, code):
261        code('std::vector< ${{self.ptype.cxx_type}} > ${{self.name}};')
262
263class ParamFactory(object):
264    def __init__(self, param_desc_class, ptype_str = None):
265        self.param_desc_class = param_desc_class
266        self.ptype_str = ptype_str
267
268    def __getattr__(self, attr):
269        if self.ptype_str:
270            attr = self.ptype_str + '.' + attr
271        return ParamFactory(self.param_desc_class, attr)
272
273    # E.g., Param.Int(5, "number of widgets")
274    def __call__(self, *args, **kwargs):
275        ptype = None
276        try:
277            ptype = allParams[self.ptype_str]
278        except KeyError:
279            # if name isn't defined yet, assume it's a SimObject, and
280            # try to resolve it later
281            pass
282        return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs)
283
284Param = ParamFactory(ParamDesc)
285VectorParam = ParamFactory(VectorParamDesc)
286
287#####################################################################
288#
289# Parameter Types
290#
291# Though native Python types could be used to specify parameter types
292# (the 'ptype' field of the Param and VectorParam classes), it's more
293# flexible to define our own set of types.  This gives us more control
294# over how Python expressions are converted to values (via the
295# __init__() constructor) and how these values are printed out (via
296# the __str__() conversion method).
297#
298#####################################################################
299
300# String-valued parameter.  Just mixin the ParamValue class with the
301# built-in str class.
302class String(ParamValue,str):
303    cxx_type = 'std::string'
304
305    @classmethod
306    def cxx_predecls(self, code):
307        code('#include <string>')
308
309    @classmethod
310    def swig_predecls(cls, code):
311        code('%include "std_string.i"')
312
313    def getValue(self):
314        return self
315
316# superclass for "numeric" parameter values, to emulate math
317# operations in a type-safe way.  e.g., a Latency times an int returns
318# a new Latency object.
319class NumericParamValue(ParamValue):
320    def __str__(self):
321        return str(self.value)
322
323    def __float__(self):
324        return float(self.value)
325
326    def __long__(self):
327        return long(self.value)
328
329    def __int__(self):
330        return int(self.value)
331
332    # hook for bounds checking
333    def _check(self):
334        return
335
336    def __mul__(self, other):
337        newobj = self.__class__(self)
338        newobj.value *= other
339        newobj._check()
340        return newobj
341
342    __rmul__ = __mul__
343
344    def __div__(self, other):
345        newobj = self.__class__(self)
346        newobj.value /= other
347        newobj._check()
348        return newobj
349
350    def __sub__(self, other):
351        newobj = self.__class__(self)
352        newobj.value -= other
353        newobj._check()
354        return newobj
355
356# Metaclass for bounds-checked integer parameters.  See CheckedInt.
357class CheckedIntType(MetaParamValue):
358    def __init__(cls, name, bases, dict):
359        super(CheckedIntType, cls).__init__(name, bases, dict)
360
361        # CheckedInt is an abstract base class, so we actually don't
362        # want to do any processing on it... the rest of this code is
363        # just for classes that derive from CheckedInt.
364        if name == 'CheckedInt':
365            return
366
367        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
368            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
369                panic("CheckedInt subclass %s must define either\n" \
370                      "    'min' and 'max' or 'size' and 'unsigned'\n",
371                      name);
372            if cls.unsigned:
373                cls.min = 0
374                cls.max = 2 ** cls.size - 1
375            else:
376                cls.min = -(2 ** (cls.size - 1))
377                cls.max = (2 ** (cls.size - 1)) - 1
378
379# Abstract superclass for bounds-checked integer parameters.  This
380# class is subclassed to generate parameter classes with specific
381# bounds.  Initialization of the min and max bounds is done in the
382# metaclass CheckedIntType.__init__.
383class CheckedInt(NumericParamValue):
384    __metaclass__ = CheckedIntType
385
386    def _check(self):
387        if not self.min <= self.value <= self.max:
388            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
389                  (self.min, self.value, self.max)
390
391    def __init__(self, value):
392        if isinstance(value, str):
393            self.value = convert.toInteger(value)
394        elif isinstance(value, (int, long, float, NumericParamValue)):
395            self.value = long(value)
396        else:
397            raise TypeError, "Can't convert object of type %s to CheckedInt" \
398                  % type(value).__name__
399        self._check()
400
401    @classmethod
402    def cxx_predecls(cls, code):
403        # most derived types require this, so we just do it here once
404        code('#include "base/types.hh"')
405
406    @classmethod
407    def swig_predecls(cls, code):
408        # most derived types require this, so we just do it here once
409        code('%import "stdint.i"')
410        code('%import "base/types.hh"')
411
412    def getValue(self):
413        return long(self.value)
414
415class Int(CheckedInt):      cxx_type = 'int';      size = 32; unsigned = False
416class Unsigned(CheckedInt): cxx_type = 'unsigned'; size = 32; unsigned = True
417
418class Int8(CheckedInt):     cxx_type =   'int8_t'; size =  8; unsigned = False
419class UInt8(CheckedInt):    cxx_type =  'uint8_t'; size =  8; unsigned = True
420class Int16(CheckedInt):    cxx_type =  'int16_t'; size = 16; unsigned = False
421class UInt16(CheckedInt):   cxx_type = 'uint16_t'; size = 16; unsigned = True
422class Int32(CheckedInt):    cxx_type =  'int32_t'; size = 32; unsigned = False
423class UInt32(CheckedInt):   cxx_type = 'uint32_t'; size = 32; unsigned = True
424class Int64(CheckedInt):    cxx_type =  'int64_t'; size = 64; unsigned = False
425class UInt64(CheckedInt):   cxx_type = 'uint64_t'; size = 64; unsigned = True
426
427class Counter(CheckedInt):  cxx_type = 'Counter';  size = 64; unsigned = True
428class Tick(CheckedInt):     cxx_type = 'Tick';     size = 64; unsigned = True
429class TcpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
430class UdpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
431
432class Percent(CheckedInt):  cxx_type = 'int'; min = 0; max = 100
433
434class Float(ParamValue, float):
435    cxx_type = 'double'
436
437    def __init__(self, value):
438        if isinstance(value, (int, long, float, NumericParamValue, Float)):
439            self.value = float(value)
440        else:
441            raise TypeError, "Can't convert object of type %s to Float" \
442                  % type(value).__name__
443
444    def getValue(self):
445        return float(self.value)
446
447class MemorySize(CheckedInt):
448    cxx_type = 'uint64_t'
449    size = 64
450    unsigned = True
451    def __init__(self, value):
452        if isinstance(value, MemorySize):
453            self.value = value.value
454        else:
455            self.value = convert.toMemorySize(value)
456        self._check()
457
458class MemorySize32(CheckedInt):
459    cxx_type = 'uint32_t'
460    size = 32
461    unsigned = True
462    def __init__(self, value):
463        if isinstance(value, MemorySize):
464            self.value = value.value
465        else:
466            self.value = convert.toMemorySize(value)
467        self._check()
468
469class Addr(CheckedInt):
470    cxx_type = 'Addr'
471    size = 64
472    unsigned = True
473    def __init__(self, value):
474        if isinstance(value, Addr):
475            self.value = value.value
476        else:
477            try:
478                self.value = convert.toMemorySize(value)
479            except TypeError:
480                self.value = long(value)
481        self._check()
482    def __add__(self, other):
483        if isinstance(other, Addr):
484            return self.value + other.value
485        else:
486            return self.value + other
487
488
489class MetaRange(MetaParamValue):
490    def __init__(cls, name, bases, dict):
491        super(MetaRange, cls).__init__(name, bases, dict)
492        if name == 'Range':
493            return
494        cls.cxx_type = 'Range< %s >' % cls.type.cxx_type
495
496class Range(ParamValue):
497    __metaclass__ = MetaRange
498    type = Int # default; can be overridden in subclasses
499    def __init__(self, *args, **kwargs):
500        def handle_kwargs(self, kwargs):
501            if 'end' in kwargs:
502                self.second = self.type(kwargs.pop('end'))
503            elif 'size' in kwargs:
504                self.second = self.first + self.type(kwargs.pop('size')) - 1
505            else:
506                raise TypeError, "Either end or size must be specified"
507
508        if len(args) == 0:
509            self.first = self.type(kwargs.pop('start'))
510            handle_kwargs(self, kwargs)
511
512        elif len(args) == 1:
513            if kwargs:
514                self.first = self.type(args[0])
515                handle_kwargs(self, kwargs)
516            elif isinstance(args[0], Range):
517                self.first = self.type(args[0].first)
518                self.second = self.type(args[0].second)
519            elif isinstance(args[0], (list, tuple)):
520                self.first = self.type(args[0][0])
521                self.second = self.type(args[0][1])
522            else:
523                self.first = self.type(0)
524                self.second = self.type(args[0]) - 1
525
526        elif len(args) == 2:
527            self.first = self.type(args[0])
528            self.second = self.type(args[1])
529        else:
530            raise TypeError, "Too many arguments specified"
531
532        if kwargs:
533            raise TypeError, "too many keywords: %s" % kwargs.keys()
534
535    def __str__(self):
536        return '%s:%s' % (self.first, self.second)
537
538    @classmethod
539    def cxx_predecls(cls, code):
540        code('#include "base/range.hh"')
541        cls.type.cxx_predecls(code)
542
543class AddrRange(Range):
544    type = Addr
545
546    @classmethod
547    def swig_predecls(cls, code):
548        code('%include "python/swig/range.i"')
549
550    def getValue(self):
551        from m5.objects.params import AddrRange
552
553        value = AddrRange()
554        value.start = long(self.first)
555        value.end = long(self.second)
556        return value
557
558class TickRange(Range):
559    type = Tick
560
561    @classmethod
562    def swig_predecls(cls, code):
563        code('%include "python/swig/range.i"')
564
565    def getValue(self):
566        from m5.objects.params import TickRange
567
568        value = TickRange()
569        value.start = long(self.first)
570        value.end = long(self.second)
571        return value
572
573# Boolean parameter type.  Python doesn't let you subclass bool, since
574# it doesn't want to let you create multiple instances of True and
575# False.  Thus this is a little more complicated than String.
576class Bool(ParamValue):
577    cxx_type = 'bool'
578    def __init__(self, value):
579        try:
580            self.value = convert.toBool(value)
581        except TypeError:
582            self.value = bool(value)
583
584    def getValue(self):
585        return bool(self.value)
586
587    def __str__(self):
588        return str(self.value)
589
590    def ini_str(self):
591        if self.value:
592            return 'true'
593        return 'false'
594
595def IncEthernetAddr(addr, val = 1):
596    bytes = map(lambda x: int(x, 16), addr.split(':'))
597    bytes[5] += val
598    for i in (5, 4, 3, 2, 1):
599        val,rem = divmod(bytes[i], 256)
600        bytes[i] = rem
601        if val == 0:
602            break
603        bytes[i - 1] += val
604    assert(bytes[0] <= 255)
605    return ':'.join(map(lambda x: '%02x' % x, bytes))
606
607_NextEthernetAddr = "00:90:00:00:00:01"
608def NextEthernetAddr():
609    global _NextEthernetAddr
610
611    value = _NextEthernetAddr
612    _NextEthernetAddr = IncEthernetAddr(_NextEthernetAddr, 1)
613    return value
614
615class EthernetAddr(ParamValue):
616    cxx_type = 'Net::EthAddr'
617
618    @classmethod
619    def cxx_predecls(cls, code):
620        code('#include "base/inet.hh"')
621
622    @classmethod
623    def swig_predecls(cls, code):
624        code('%include "python/swig/inet.i"')
625
626    def __init__(self, value):
627        if value == NextEthernetAddr:
628            self.value = value
629            return
630
631        if not isinstance(value, str):
632            raise TypeError, "expected an ethernet address and didn't get one"
633
634        bytes = value.split(':')
635        if len(bytes) != 6:
636            raise TypeError, 'invalid ethernet address %s' % value
637
638        for byte in bytes:
639            if not 0 <= int(byte) <= 256:
640                raise TypeError, 'invalid ethernet address %s' % value
641
642        self.value = value
643
644    def unproxy(self, base):
645        if self.value == NextEthernetAddr:
646            return EthernetAddr(self.value())
647        return self
648
649    def getValue(self):
650        from m5.objects.params import EthAddr
651        return EthAddr(self.value)
652
653    def ini_str(self):
654        return self.value
655
656time_formats = [ "%a %b %d %H:%M:%S %Z %Y",
657                 "%a %b %d %H:%M:%S %Z %Y",
658                 "%Y/%m/%d %H:%M:%S",
659                 "%Y/%m/%d %H:%M",
660                 "%Y/%m/%d",
661                 "%m/%d/%Y %H:%M:%S",
662                 "%m/%d/%Y %H:%M",
663                 "%m/%d/%Y",
664                 "%m/%d/%y %H:%M:%S",
665                 "%m/%d/%y %H:%M",
666                 "%m/%d/%y"]
667
668
669def parse_time(value):
670    from time import gmtime, strptime, struct_time, time
671    from datetime import datetime, date
672
673    if isinstance(value, struct_time):
674        return value
675
676    if isinstance(value, (int, long)):
677        return gmtime(value)
678
679    if isinstance(value, (datetime, date)):
680        return value.timetuple()
681
682    if isinstance(value, str):
683        if value in ('Now', 'Today'):
684            return time.gmtime(time.time())
685
686        for format in time_formats:
687            try:
688                return strptime(value, format)
689            except ValueError:
690                pass
691
692    raise ValueError, "Could not parse '%s' as a time" % value
693
694class Time(ParamValue):
695    cxx_type = 'tm'
696
697    @classmethod
698    def cxx_predecls(cls, code):
699        code('#include <time.h>')
700
701    @classmethod
702    def swig_predecls(cls, code):
703        code('%include "python/swig/time.i"')
704
705    def __init__(self, value):
706        self.value = parse_time(value)
707
708    def getValue(self):
709        from m5.objects.params import tm
710
711        c_time = tm()
712        py_time = self.value
713
714        # UNIX is years since 1900
715        c_time.tm_year = py_time.tm_year - 1900;
716
717        # Python starts at 1, UNIX starts at 0
718        c_time.tm_mon =  py_time.tm_mon - 1;
719        c_time.tm_mday = py_time.tm_mday;
720        c_time.tm_hour = py_time.tm_hour;
721        c_time.tm_min = py_time.tm_min;
722        c_time.tm_sec = py_time.tm_sec;
723
724        # Python has 0 as Monday, UNIX is 0 as sunday
725        c_time.tm_wday = py_time.tm_wday + 1
726        if c_time.tm_wday > 6:
727            c_time.tm_wday -= 7;
728
729        # Python starts at 1, Unix starts at 0
730        c_time.tm_yday = py_time.tm_yday - 1;
731
732        return c_time
733
734    def __str__(self):
735        return time.asctime(self.value)
736
737    def ini_str(self):
738        return str(self)
739
740# Enumerated types are a little more complex.  The user specifies the
741# type as Enum(foo) where foo is either a list or dictionary of
742# alternatives (typically strings, but not necessarily so).  (In the
743# long run, the integer value of the parameter will be the list index
744# or the corresponding dictionary value.  For now, since we only check
745# that the alternative is valid and then spit it into a .ini file,
746# there's not much point in using the dictionary.)
747
748# What Enum() must do is generate a new type encapsulating the
749# provided list/dictionary so that specific values of the parameter
750# can be instances of that type.  We define two hidden internal
751# classes (_ListEnum and _DictEnum) to serve as base classes, then
752# derive the new type from the appropriate base class on the fly.
753
754allEnums = {}
755# Metaclass for Enum types
756class MetaEnum(MetaParamValue):
757    def __new__(mcls, name, bases, dict):
758        assert name not in allEnums
759
760        cls = super(MetaEnum, mcls).__new__(mcls, name, bases, dict)
761        allEnums[name] = cls
762        return cls
763
764    def __init__(cls, name, bases, init_dict):
765        if init_dict.has_key('map'):
766            if not isinstance(cls.map, dict):
767                raise TypeError, "Enum-derived class attribute 'map' " \
768                      "must be of type dict"
769            # build list of value strings from map
770            cls.vals = cls.map.keys()
771            cls.vals.sort()
772        elif init_dict.has_key('vals'):
773            if not isinstance(cls.vals, list):
774                raise TypeError, "Enum-derived class attribute 'vals' " \
775                      "must be of type list"
776            # build string->value map from vals sequence
777            cls.map = {}
778            for idx,val in enumerate(cls.vals):
779                cls.map[val] = idx
780        else:
781            raise TypeError, "Enum-derived class must define "\
782                  "attribute 'map' or 'vals'"
783
784        cls.cxx_type = 'Enums::%s' % name
785
786        super(MetaEnum, cls).__init__(name, bases, init_dict)
787
788    # Generate C++ class declaration for this enum type.
789    # Note that we wrap the enum in a class/struct to act as a namespace,
790    # so that the enum strings can be brief w/o worrying about collisions.
791    def cxx_decl(cls, code):
792        name = cls.__name__
793        code('''\
794#ifndef __ENUM__${name}__
795#define __ENUM__${name}__
796
797namespace Enums {
798    enum $name {
799''')
800        code.indent(2)
801        for val in cls.vals:
802            code('$val = ${{cls.map[val]}},')
803        code('Num_$name = ${{len(cls.vals)}},')
804        code.dedent(2)
805        code('''\
806    };
807extern const char *${name}Strings[Num_${name}];
808}
809
810#endif // __ENUM__${name}__
811''')
812
813    def cxx_def(cls, code):
814        name = cls.__name__
815        code('''\
816#include "enums/${name}.hh"
817namespace Enums {
818    const char *${name}Strings[Num_${name}] =
819    {
820''')
821        code.indent(2)
822        for val in cls.vals:
823            code('"$val",')
824        code.dedent(2)
825        code('''
826    };
827/* namespace Enums */ }
828''')
829
830# Base class for enum types.
831class Enum(ParamValue):
832    __metaclass__ = MetaEnum
833    vals = []
834
835    def __init__(self, value):
836        if value not in self.map:
837            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
838                  % (value, self.vals)
839        self.value = value
840
841    def getValue(self):
842        return int(self.map[self.value])
843
844    def __str__(self):
845        return self.value
846
847# how big does a rounding error need to be before we warn about it?
848frequency_tolerance = 0.001  # 0.1%
849
850class TickParamValue(NumericParamValue):
851    cxx_type = 'Tick'
852
853    @classmethod
854    def cxx_predecls(cls, code):
855        code('#include "base/types.hh"')
856
857    @classmethod
858    def swig_predecls(cls, code):
859        code('%import "stdint.i"')
860        code('%import "base/types.hh"')
861
862    def getValue(self):
863        return long(self.value)
864
865class Latency(TickParamValue):
866    def __init__(self, value):
867        if isinstance(value, (Latency, Clock)):
868            self.ticks = value.ticks
869            self.value = value.value
870        elif isinstance(value, Frequency):
871            self.ticks = value.ticks
872            self.value = 1.0 / value.value
873        elif value.endswith('t'):
874            self.ticks = True
875            self.value = int(value[:-1])
876        else:
877            self.ticks = False
878            self.value = convert.toLatency(value)
879
880    def __getattr__(self, attr):
881        if attr in ('latency', 'period'):
882            return self
883        if attr == 'frequency':
884            return Frequency(self)
885        raise AttributeError, "Latency object has no attribute '%s'" % attr
886
887    def getValue(self):
888        if self.ticks or self.value == 0:
889            value = self.value
890        else:
891            value = ticks.fromSeconds(self.value)
892        return long(value)
893
894    # convert latency to ticks
895    def ini_str(self):
896        return '%d' % self.getValue()
897
898class Frequency(TickParamValue):
899    def __init__(self, value):
900        if isinstance(value, (Latency, Clock)):
901            if value.value == 0:
902                self.value = 0
903            else:
904                self.value = 1.0 / value.value
905            self.ticks = value.ticks
906        elif isinstance(value, Frequency):
907            self.value = value.value
908            self.ticks = value.ticks
909        else:
910            self.ticks = False
911            self.value = convert.toFrequency(value)
912
913    def __getattr__(self, attr):
914        if attr == 'frequency':
915            return self
916        if attr in ('latency', 'period'):
917            return Latency(self)
918        raise AttributeError, "Frequency object has no attribute '%s'" % attr
919
920    # convert latency to ticks
921    def getValue(self):
922        if self.ticks or self.value == 0:
923            value = self.value
924        else:
925            value = ticks.fromSeconds(1.0 / self.value)
926        return long(value)
927
928    def ini_str(self):
929        return '%d' % self.getValue()
930
931# A generic frequency and/or Latency value.  Value is stored as a latency,
932# but to avoid ambiguity this object does not support numeric ops (* or /).
933# An explicit conversion to a Latency or Frequency must be made first.
934class Clock(ParamValue):
935    cxx_type = 'Tick'
936
937    @classmethod
938    def cxx_predecls(cls, code):
939        code('#include "base/types.hh"')
940
941    @classmethod
942    def swig_predecls(cls, code):
943        code('%import "stdint.i"')
944        code('%import "base/types.hh"')
945
946    def __init__(self, value):
947        if isinstance(value, (Latency, Clock)):
948            self.ticks = value.ticks
949            self.value = value.value
950        elif isinstance(value, Frequency):
951            self.ticks = value.ticks
952            self.value = 1.0 / value.value
953        elif value.endswith('t'):
954            self.ticks = True
955            self.value = int(value[:-1])
956        else:
957            self.ticks = False
958            self.value = convert.anyToLatency(value)
959
960    def __getattr__(self, attr):
961        if attr == 'frequency':
962            return Frequency(self)
963        if attr in ('latency', 'period'):
964            return Latency(self)
965        raise AttributeError, "Frequency object has no attribute '%s'" % attr
966
967    def getValue(self):
968        return self.period.getValue()
969
970    def ini_str(self):
971        return self.period.ini_str()
972
973class NetworkBandwidth(float,ParamValue):
974    cxx_type = 'float'
975    def __new__(cls, value):
976        # convert to bits per second
977        val = convert.toNetworkBandwidth(value)
978        return super(cls, NetworkBandwidth).__new__(cls, val)
979
980    def __str__(self):
981        return str(self.val)
982
983    def getValue(self):
984        # convert to seconds per byte
985        value = 8.0 / float(self)
986        # convert to ticks per byte
987        value = ticks.fromSeconds(value)
988        return float(value)
989
990    def ini_str(self):
991        return '%f' % self.getValue()
992
993class MemoryBandwidth(float,ParamValue):
994    cxx_type = 'float'
995    def __new__(cls, value):
996        # we want the number of ticks per byte of data
997        val = convert.toMemoryBandwidth(value)
998        return super(cls, MemoryBandwidth).__new__(cls, val)
999
1000    def __str__(self):
1001        return str(self.val)
1002
1003    def getValue(self):
1004        # convert to seconds per byte
1005        value = float(self)
1006        if value:
1007            value = 1.0 / float(self)
1008        # convert to ticks per byte
1009        value = ticks.fromSeconds(value)
1010        return float(value)
1011
1012    def ini_str(self):
1013        return '%f' % self.getValue()
1014
1015#
1016# "Constants"... handy aliases for various values.
1017#
1018
1019# Special class for NULL pointers.  Note the special check in
1020# make_param_value() above that lets these be assigned where a
1021# SimObject is required.
1022# only one copy of a particular node
1023class NullSimObject(object):
1024    __metaclass__ = Singleton
1025
1026    def __call__(cls):
1027        return cls
1028
1029    def _instantiate(self, parent = None, path = ''):
1030        pass
1031
1032    def ini_str(self):
1033        return 'Null'
1034
1035    def unproxy(self, base):
1036        return self
1037
1038    def set_path(self, parent, name):
1039        pass
1040
1041    def __str__(self):
1042        return 'Null'
1043
1044    def getValue(self):
1045        return None
1046
1047# The only instance you'll ever need...
1048NULL = NullSimObject()
1049
1050def isNullPointer(value):
1051    return isinstance(value, NullSimObject)
1052
1053# Some memory range specifications use this as a default upper bound.
1054MaxAddr = Addr.max
1055MaxTick = Tick.max
1056AllMemory = AddrRange(0, MaxAddr)
1057
1058
1059#####################################################################
1060#
1061# Port objects
1062#
1063# Ports are used to interconnect objects in the memory system.
1064#
1065#####################################################################
1066
1067# Port reference: encapsulates a reference to a particular port on a
1068# particular SimObject.
1069class PortRef(object):
1070    def __init__(self, simobj, name):
1071        assert(isSimObject(simobj) or isSimObjectClass(simobj))
1072        self.simobj = simobj
1073        self.name = name
1074        self.peer = None   # not associated with another port yet
1075        self.ccConnected = False # C++ port connection done?
1076        self.index = -1  # always -1 for non-vector ports
1077
1078    def __str__(self):
1079        return '%s.%s' % (self.simobj, self.name)
1080
1081    # for config.ini, print peer's name (not ours)
1082    def ini_str(self):
1083        return str(self.peer)
1084
1085    def __getattr__(self, attr):
1086        if attr == 'peerObj':
1087            # shorthand for proxies
1088            return self.peer.simobj
1089        raise AttributeError, "'%s' object has no attribute '%s'" % \
1090              (self.__class__.__name__, attr)
1091
1092    # Full connection is symmetric (both ways).  Called via
1093    # SimObject.__setattr__ as a result of a port assignment, e.g.,
1094    # "obj1.portA = obj2.portB", or via VectorPortElementRef.__setitem__,
1095    # e.g., "obj1.portA[3] = obj2.portB".
1096    def connect(self, other):
1097        if isinstance(other, VectorPortRef):
1098            # reference to plain VectorPort is implicit append
1099            other = other._get_next()
1100        if self.peer and not proxy.isproxy(self.peer):
1101            print "warning: overwriting port", self, \
1102                  "value", self.peer, "with", other
1103            self.peer.peer = None
1104        self.peer = other
1105        if proxy.isproxy(other):
1106            other.set_param_desc(PortParamDesc())
1107        elif isinstance(other, PortRef):
1108            if other.peer is not self:
1109                other.connect(self)
1110        else:
1111            raise TypeError, \
1112                  "assigning non-port reference '%s' to port '%s'" \
1113                  % (other, self)
1114
1115    def clone(self, simobj, memo):
1116        if memo.has_key(self):
1117            return memo[self]
1118        newRef = copy.copy(self)
1119        memo[self] = newRef
1120        newRef.simobj = simobj
1121        assert(isSimObject(newRef.simobj))
1122        if self.peer and not proxy.isproxy(self.peer):
1123            peerObj = self.peer.simobj(_memo=memo)
1124            newRef.peer = self.peer.clone(peerObj, memo)
1125            assert(not isinstance(newRef.peer, VectorPortRef))
1126        return newRef
1127
1128    def unproxy(self, simobj):
1129        assert(simobj is self.simobj)
1130        if proxy.isproxy(self.peer):
1131            try:
1132                realPeer = self.peer.unproxy(self.simobj)
1133            except:
1134                print "Error in unproxying port '%s' of %s" % \
1135                      (self.name, self.simobj.path())
1136                raise
1137            self.connect(realPeer)
1138
1139    # Call C++ to create corresponding port connection between C++ objects
1140    def ccConnect(self):
1141        from m5.objects.params import connectPorts
1142
1143        if self.ccConnected: # already done this
1144            return
1145        peer = self.peer
1146        if not self.peer: # nothing to connect to
1147            return
1148        try:
1149            connectPorts(self.simobj.getCCObject(), self.name, self.index,
1150                         peer.simobj.getCCObject(), peer.name, peer.index)
1151        except:
1152            print "Error connecting port %s.%s to %s.%s" % \
1153                  (self.simobj.path(), self.name,
1154                   peer.simobj.path(), peer.name)
1155            raise
1156        self.ccConnected = True
1157        peer.ccConnected = True
1158
1159# A reference to an individual element of a VectorPort... much like a
1160# PortRef, but has an index.
1161class VectorPortElementRef(PortRef):
1162    def __init__(self, simobj, name, index):
1163        PortRef.__init__(self, simobj, name)
1164        self.index = index
1165
1166    def __str__(self):
1167        return '%s.%s[%d]' % (self.simobj, self.name, self.index)
1168
1169# A reference to a complete vector-valued port (not just a single element).
1170# Can be indexed to retrieve individual VectorPortElementRef instances.
1171class VectorPortRef(object):
1172    def __init__(self, simobj, name):
1173        assert(isSimObject(simobj) or isSimObjectClass(simobj))
1174        self.simobj = simobj
1175        self.name = name
1176        self.elements = []
1177
1178    def __str__(self):
1179        return '%s.%s[:]' % (self.simobj, self.name)
1180
1181    # for config.ini, print peer's name (not ours)
1182    def ini_str(self):
1183        return ' '.join([el.ini_str() for el in self.elements])
1184
1185    def __getitem__(self, key):
1186        if not isinstance(key, int):
1187            raise TypeError, "VectorPort index must be integer"
1188        if key >= len(self.elements):
1189            # need to extend list
1190            ext = [VectorPortElementRef(self.simobj, self.name, i)
1191                   for i in range(len(self.elements), key+1)]
1192            self.elements.extend(ext)
1193        return self.elements[key]
1194
1195    def _get_next(self):
1196        return self[len(self.elements)]
1197
1198    def __setitem__(self, key, value):
1199        if not isinstance(key, int):
1200            raise TypeError, "VectorPort index must be integer"
1201        self[key].connect(value)
1202
1203    def connect(self, other):
1204        if isinstance(other, (list, tuple)):
1205            # Assign list of port refs to vector port.
1206            # For now, append them... not sure if that's the right semantics
1207            # or if it should replace the current vector.
1208            for ref in other:
1209                self._get_next().connect(ref)
1210        else:
1211            # scalar assignment to plain VectorPort is implicit append
1212            self._get_next().connect(other)
1213
1214    def clone(self, simobj, memo):
1215        if memo.has_key(self):
1216            return memo[self]
1217        newRef = copy.copy(self)
1218        memo[self] = newRef
1219        newRef.simobj = simobj
1220        assert(isSimObject(newRef.simobj))
1221        newRef.elements = [el.clone(simobj, memo) for el in self.elements]
1222        return newRef
1223
1224    def unproxy(self, simobj):
1225        [el.unproxy(simobj) for el in self.elements]
1226
1227    def ccConnect(self):
1228        [el.ccConnect() for el in self.elements]
1229
1230# Port description object.  Like a ParamDesc object, this represents a
1231# logical port in the SimObject class, not a particular port on a
1232# SimObject instance.  The latter are represented by PortRef objects.
1233class Port(object):
1234    # Port("description") or Port(default, "description")
1235    def __init__(self, *args):
1236        if len(args) == 1:
1237            self.desc = args[0]
1238        elif len(args) == 2:
1239            self.default = args[0]
1240            self.desc = args[1]
1241        else:
1242            raise TypeError, 'wrong number of arguments'
1243        # self.name is set by SimObject class on assignment
1244        # e.g., pio_port = Port("blah") sets self.name to 'pio_port'
1245
1246    # Generate a PortRef for this port on the given SimObject with the
1247    # given name
1248    def makeRef(self, simobj):
1249        return PortRef(simobj, self.name)
1250
1251    # Connect an instance of this port (on the given SimObject with
1252    # the given name) with the port described by the supplied PortRef
1253    def connect(self, simobj, ref):
1254        self.makeRef(simobj).connect(ref)
1255
1256# VectorPort description object.  Like Port, but represents a vector
1257# of connections (e.g., as on a Bus).
1258class VectorPort(Port):
1259    def __init__(self, *args):
1260        Port.__init__(self, *args)
1261        self.isVec = True
1262
1263    def makeRef(self, simobj):
1264        return VectorPortRef(simobj, self.name)
1265
1266# 'Fake' ParamDesc for Port references to assign to the _pdesc slot of
1267# proxy objects (via set_param_desc()) so that proxy error messages
1268# make sense.
1269class PortParamDesc(object):
1270    __metaclass__ = Singleton
1271
1272    ptype_str = 'Port'
1273    ptype = Port
1274
1275baseEnums = allEnums.copy()
1276baseParams = allParams.copy()
1277
1278def clear():
1279    global allEnums, allParams
1280
1281    allEnums = baseEnums.copy()
1282    allParams = baseParams.copy()
1283
1284__all__ = ['Param', 'VectorParam',
1285           'Enum', 'Bool', 'String', 'Float',
1286           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1287           'Int32', 'UInt32', 'Int64', 'UInt64',
1288           'Counter', 'Addr', 'Tick', 'Percent',
1289           'TcpPort', 'UdpPort', 'EthernetAddr',
1290           'MemorySize', 'MemorySize32',
1291           'Latency', 'Frequency', 'Clock',
1292           'NetworkBandwidth', 'MemoryBandwidth',
1293           'Range', 'AddrRange', 'TickRange',
1294           'MaxAddr', 'MaxTick', 'AllMemory',
1295           'Time',
1296           'NextEthernetAddr', 'NULL',
1297           'Port', 'VectorPort']
1298
1299import SimObject
1300