params.py revision 3932:62e915bb6704
111986Sandreas.sandberg@arm.com# Copyright (c) 2004-2006 The Regents of The University of Michigan
211986Sandreas.sandberg@arm.com# All rights reserved.
311986Sandreas.sandberg@arm.com#
411986Sandreas.sandberg@arm.com# Redistribution and use in source and binary forms, with or without
511986Sandreas.sandberg@arm.com# modification, are permitted provided that the following conditions are
611986Sandreas.sandberg@arm.com# met: redistributions of source code must retain the above copyright
711986Sandreas.sandberg@arm.com# notice, this list of conditions and the following disclaimer;
811986Sandreas.sandberg@arm.com# redistributions in binary form must reproduce the above copyright
911986Sandreas.sandberg@arm.com# notice, this list of conditions and the following disclaimer in the
1011986Sandreas.sandberg@arm.com# documentation and/or other materials provided with the distribution;
1111986Sandreas.sandberg@arm.com# neither the name of the copyright holders nor the names of its
1211986Sandreas.sandberg@arm.com# contributors may be used to endorse or promote products derived from
1311986Sandreas.sandberg@arm.com# this software without specific prior written permission.
1411986Sandreas.sandberg@arm.com#
1511986Sandreas.sandberg@arm.com# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1611986Sandreas.sandberg@arm.com# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1711986Sandreas.sandberg@arm.com# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1811986Sandreas.sandberg@arm.com# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1911986Sandreas.sandberg@arm.com# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2011986Sandreas.sandberg@arm.com# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2111986Sandreas.sandberg@arm.com# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2211986Sandreas.sandberg@arm.com# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2311986Sandreas.sandberg@arm.com# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2411986Sandreas.sandberg@arm.com# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2511986Sandreas.sandberg@arm.com# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2611986Sandreas.sandberg@arm.com#
2711986Sandreas.sandberg@arm.com# Authors: Steve Reinhardt
2811986Sandreas.sandberg@arm.com#          Nathan Binkert
2911986Sandreas.sandberg@arm.com
3011986Sandreas.sandberg@arm.com#####################################################################
3111986Sandreas.sandberg@arm.com#
3211986Sandreas.sandberg@arm.com# Parameter description classes
33#
34# The _params dictionary in each class maps parameter names to either
35# a Param or a VectorParam object.  These objects contain the
36# parameter description string, the parameter type, and the default
37# value (if any).  The convert() method on these objects is used to
38# force whatever value is assigned to the parameter to the appropriate
39# type.
40#
41# Note that the default values are loaded into the class's attribute
42# space when the parameter dictionary is initialized (in
43# MetaSimObject._new_param()); after that point they aren't used.
44#
45#####################################################################
46
47import copy
48import datetime
49import inspect
50import sys
51import time
52
53import convert
54from util import *
55
56# Dummy base class to identify types that are legitimate for SimObject
57# parameters.
58class ParamValue(object):
59
60    cxx_predecls = []
61    swig_predecls = []
62
63    # default for printing to .ini file is regular string conversion.
64    # will be overridden in some cases
65    def ini_str(self):
66        return str(self)
67
68    # allows us to blithely call unproxy() on things without checking
69    # if they're really proxies or not
70    def unproxy(self, base):
71        return self
72
73# Regular parameter description.
74class ParamDesc(object):
75    def __init__(self, ptype_str, ptype, *args, **kwargs):
76        self.ptype_str = ptype_str
77        # remember ptype only if it is provided
78        if ptype != None:
79            self.ptype = ptype
80
81        if args:
82            if len(args) == 1:
83                self.desc = args[0]
84            elif len(args) == 2:
85                self.default = args[0]
86                self.desc = args[1]
87            else:
88                raise TypeError, 'too many arguments'
89
90        if kwargs.has_key('desc'):
91            assert(not hasattr(self, 'desc'))
92            self.desc = kwargs['desc']
93            del kwargs['desc']
94
95        if kwargs.has_key('default'):
96            assert(not hasattr(self, 'default'))
97            self.default = kwargs['default']
98            del kwargs['default']
99
100        if kwargs:
101            raise TypeError, 'extra unknown kwargs %s' % kwargs
102
103        if not hasattr(self, 'desc'):
104            raise TypeError, 'desc attribute missing'
105
106    def __getattr__(self, attr):
107        if attr == 'ptype':
108            try:
109                ptype = eval(self.ptype_str, objects.__dict__)
110                if not isinstance(ptype, type):
111                    raise NameError
112                self.ptype = ptype
113                return ptype
114            except NameError:
115                raise TypeError, \
116                      "Param qualifier '%s' is not a type" % self.ptype_str
117        raise AttributeError, "'%s' object has no attribute '%s'" % \
118              (type(self).__name__, attr)
119
120    def convert(self, value):
121        if isinstance(value, proxy.BaseProxy):
122            value.set_param_desc(self)
123            return value
124        if not hasattr(self, 'ptype') and isNullPointer(value):
125            # deferred evaluation of SimObject; continue to defer if
126            # we're just assigning a null pointer
127            return value
128        if isinstance(value, self.ptype):
129            return value
130        if isNullPointer(value) and isSimObjectClass(self.ptype):
131            return value
132        return self.ptype(value)
133
134    def cxx_predecls(self):
135        return self.ptype.cxx_predecls
136
137    def swig_predecls(self):
138        return self.ptype.swig_predecls
139
140    def cxx_decl(self):
141        return '%s %s;' % (self.ptype.cxx_type, self.name)
142
143# Vector-valued parameter description.  Just like ParamDesc, except
144# that the value is a vector (list) of the specified type instead of a
145# single value.
146
147class VectorParamValue(list):
148    def ini_str(self):
149        return ' '.join([v.ini_str() for v in self])
150
151    def unproxy(self, base):
152        return [v.unproxy(base) for v in self]
153
154class SimObjVector(VectorParamValue):
155    def print_ini(self):
156        for v in self:
157            v.print_ini()
158
159class VectorParamDesc(ParamDesc):
160    # Convert assigned value to appropriate type.  If the RHS is not a
161    # list or tuple, it generates a single-element list.
162    def convert(self, value):
163        if isinstance(value, (list, tuple)):
164            # list: coerce each element into new list
165            tmp_list = [ ParamDesc.convert(self, v) for v in value ]
166            if isSimObjectSequence(tmp_list):
167                return SimObjVector(tmp_list)
168            else:
169                return VectorParamValue(tmp_list)
170        else:
171            # singleton: leave it be (could coerce to a single-element
172            # list here, but for some historical reason we don't...
173            return ParamDesc.convert(self, value)
174
175    def cxx_predecls(self):
176        return ['#include <vector>'] + self.ptype.cxx_predecls
177
178    def swig_predecls(self):
179        return ['%include "std_vector.i"'] + self.ptype.swig_predecls
180
181    def cxx_decl(self):
182        return 'std::vector< %s > %s;' % (self.ptype.cxx_type, self.name)
183
184class ParamFactory(object):
185    def __init__(self, param_desc_class, ptype_str = None):
186        self.param_desc_class = param_desc_class
187        self.ptype_str = ptype_str
188
189    def __getattr__(self, attr):
190        if self.ptype_str:
191            attr = self.ptype_str + '.' + attr
192        return ParamFactory(self.param_desc_class, attr)
193
194    # E.g., Param.Int(5, "number of widgets")
195    def __call__(self, *args, **kwargs):
196        caller_frame = inspect.currentframe().f_back
197        ptype = None
198        try:
199            ptype = eval(self.ptype_str,
200                         caller_frame.f_globals, caller_frame.f_locals)
201            if not isinstance(ptype, type):
202                raise TypeError, \
203                      "Param qualifier is not a type: %s" % ptype
204        except NameError:
205            # if name isn't defined yet, assume it's a SimObject, and
206            # try to resolve it later
207            pass
208        return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs)
209
210Param = ParamFactory(ParamDesc)
211VectorParam = ParamFactory(VectorParamDesc)
212
213#####################################################################
214#
215# Parameter Types
216#
217# Though native Python types could be used to specify parameter types
218# (the 'ptype' field of the Param and VectorParam classes), it's more
219# flexible to define our own set of types.  This gives us more control
220# over how Python expressions are converted to values (via the
221# __init__() constructor) and how these values are printed out (via
222# the __str__() conversion method).
223#
224#####################################################################
225
226# String-valued parameter.  Just mixin the ParamValue class with the
227# built-in str class.
228class String(ParamValue,str):
229    cxx_type = 'std::string'
230    cxx_predecls = ['#include <string>']
231    swig_predecls = ['%include "std_string.i"\n' +
232                     '%apply const std::string& {std::string *};']
233    pass
234
235# superclass for "numeric" parameter values, to emulate math
236# operations in a type-safe way.  e.g., a Latency times an int returns
237# a new Latency object.
238class NumericParamValue(ParamValue):
239    def __str__(self):
240        return str(self.value)
241
242    def __float__(self):
243        return float(self.value)
244
245    def __long__(self):
246        return long(self.value)
247
248    def __int__(self):
249        return int(self.value)
250
251    # hook for bounds checking
252    def _check(self):
253        return
254
255    def __mul__(self, other):
256        newobj = self.__class__(self)
257        newobj.value *= other
258        newobj._check()
259        return newobj
260
261    __rmul__ = __mul__
262
263    def __div__(self, other):
264        newobj = self.__class__(self)
265        newobj.value /= other
266        newobj._check()
267        return newobj
268
269    def __sub__(self, other):
270        newobj = self.__class__(self)
271        newobj.value -= other
272        newobj._check()
273        return newobj
274
275# Metaclass for bounds-checked integer parameters.  See CheckedInt.
276class CheckedIntType(type):
277    def __init__(cls, name, bases, dict):
278        super(CheckedIntType, cls).__init__(name, bases, dict)
279
280        # CheckedInt is an abstract base class, so we actually don't
281        # want to do any processing on it... the rest of this code is
282        # just for classes that derive from CheckedInt.
283        if name == 'CheckedInt':
284            return
285
286        if not cls.cxx_predecls:
287            # most derived types require this, so we just do it here once
288            cls.cxx_predecls = ['#include "sim/host.hh"']
289
290        if not cls.swig_predecls:
291            # most derived types require this, so we just do it here once
292            cls.swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
293                                 '%import "sim/host.hh"']
294
295        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
296            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
297                panic("CheckedInt subclass %s must define either\n" \
298                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
299                      % name);
300            if cls.unsigned:
301                cls.min = 0
302                cls.max = 2 ** cls.size - 1
303            else:
304                cls.min = -(2 ** (cls.size - 1))
305                cls.max = (2 ** (cls.size - 1)) - 1
306
307# Abstract superclass for bounds-checked integer parameters.  This
308# class is subclassed to generate parameter classes with specific
309# bounds.  Initialization of the min and max bounds is done in the
310# metaclass CheckedIntType.__init__.
311class CheckedInt(NumericParamValue):
312    __metaclass__ = CheckedIntType
313
314    def _check(self):
315        if not self.min <= self.value <= self.max:
316            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
317                  (self.min, self.value, self.max)
318
319    def __init__(self, value):
320        if isinstance(value, str):
321            self.value = convert.toInteger(value)
322        elif isinstance(value, (int, long, float, NumericParamValue)):
323            self.value = long(value)
324        else:
325            raise TypeError, "Can't convert object of type %s to CheckedInt" \
326                  % type(value).__name__
327        self._check()
328
329class Int(CheckedInt):      cxx_type = 'int';      size = 32; unsigned = False
330class Unsigned(CheckedInt): cxx_type = 'unsigned'; size = 32; unsigned = True
331
332class Int8(CheckedInt):     cxx_type =   'int8_t'; size =  8; unsigned = False
333class UInt8(CheckedInt):    cxx_type =  'uint8_t'; size =  8; unsigned = True
334class Int16(CheckedInt):    cxx_type =  'int16_t'; size = 16; unsigned = False
335class UInt16(CheckedInt):   cxx_type = 'uint16_t'; size = 16; unsigned = True
336class Int32(CheckedInt):    cxx_type =  'int32_t'; size = 32; unsigned = False
337class UInt32(CheckedInt):   cxx_type = 'uint32_t'; size = 32; unsigned = True
338class Int64(CheckedInt):    cxx_type =  'int64_t'; size = 64; unsigned = False
339class UInt64(CheckedInt):   cxx_type = 'uint64_t'; size = 64; unsigned = True
340
341class Counter(CheckedInt):  cxx_type = 'Counter';  size = 64; unsigned = True
342class Tick(CheckedInt):     cxx_type = 'Tick';     size = 64; unsigned = True
343class TcpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
344class UdpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
345
346class Percent(CheckedInt):  cxx_type = 'int'; min = 0; max = 100
347
348class Float(ParamValue, float):
349    pass
350
351class MemorySize(CheckedInt):
352    cxx_type = 'uint64_t'
353    size = 64
354    unsigned = True
355    def __init__(self, value):
356        if isinstance(value, MemorySize):
357            self.value = value.value
358        else:
359            self.value = convert.toMemorySize(value)
360        self._check()
361
362class MemorySize32(CheckedInt):
363    size = 32
364    unsigned = True
365    def __init__(self, value):
366        if isinstance(value, MemorySize):
367            self.value = value.value
368        else:
369            self.value = convert.toMemorySize(value)
370        self._check()
371
372class Addr(CheckedInt):
373    cxx_type = 'Addr'
374    cxx_predecls = ['#include "targetarch/isa_traits.hh"']
375    size = 64
376    unsigned = True
377    def __init__(self, value):
378        if isinstance(value, Addr):
379            self.value = value.value
380        else:
381            try:
382                self.value = convert.toMemorySize(value)
383            except TypeError:
384                self.value = long(value)
385        self._check()
386    def __add__(self, other):
387        if isinstance(other, Addr):
388            return self.value + other.value
389        else:
390            return self.value + other
391
392
393class MetaRange(type):
394    def __init__(cls, name, bases, dict):
395        super(MetaRange, cls).__init__(name, bases, dict)
396        if name == 'Range':
397            return
398        cls.cxx_type = 'Range< %s >' % cls.type.cxx_type
399        cls.cxx_predecls = \
400                       ['#include "base/range.hh"'] + cls.type.cxx_predecls
401
402class Range(ParamValue):
403    __metaclass__ = MetaRange
404    type = Int # default; can be overridden in subclasses
405    def __init__(self, *args, **kwargs):
406        def handle_kwargs(self, kwargs):
407            if 'end' in kwargs:
408                self.second = self.type(kwargs.pop('end'))
409            elif 'size' in kwargs:
410                self.second = self.first + self.type(kwargs.pop('size')) - 1
411            else:
412                raise TypeError, "Either end or size must be specified"
413
414        if len(args) == 0:
415            self.first = self.type(kwargs.pop('start'))
416            handle_kwargs(self, kwargs)
417
418        elif len(args) == 1:
419            if kwargs:
420                self.first = self.type(args[0])
421                handle_kwargs(self, kwargs)
422            elif isinstance(args[0], Range):
423                self.first = self.type(args[0].first)
424                self.second = self.type(args[0].second)
425            else:
426                self.first = self.type(0)
427                self.second = self.type(args[0]) - 1
428
429        elif len(args) == 2:
430            self.first = self.type(args[0])
431            self.second = self.type(args[1])
432        else:
433            raise TypeError, "Too many arguments specified"
434
435        if kwargs:
436            raise TypeError, "too many keywords: %s" % kwargs.keys()
437
438    def __str__(self):
439        return '%s:%s' % (self.first, self.second)
440
441class AddrRange(Range):
442    type = Addr
443
444class TickRange(Range):
445    type = Tick
446
447# Boolean parameter type.  Python doesn't let you subclass bool, since
448# it doesn't want to let you create multiple instances of True and
449# False.  Thus this is a little more complicated than String.
450class Bool(ParamValue):
451    cxx_type = 'bool'
452    def __init__(self, value):
453        try:
454            self.value = convert.toBool(value)
455        except TypeError:
456            self.value = bool(value)
457
458    def __str__(self):
459        return str(self.value)
460
461    def ini_str(self):
462        if self.value:
463            return 'true'
464        return 'false'
465
466def IncEthernetAddr(addr, val = 1):
467    bytes = map(lambda x: int(x, 16), addr.split(':'))
468    bytes[5] += val
469    for i in (5, 4, 3, 2, 1):
470        val,rem = divmod(bytes[i], 256)
471        bytes[i] = rem
472        if val == 0:
473            break
474        bytes[i - 1] += val
475    assert(bytes[0] <= 255)
476    return ':'.join(map(lambda x: '%02x' % x, bytes))
477
478class NextEthernetAddr(object):
479    addr = "00:90:00:00:00:01"
480
481    def __init__(self, inc = 1):
482        self.value = NextEthernetAddr.addr
483        NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc)
484
485class EthernetAddr(ParamValue):
486    cxx_type = 'Net::EthAddr'
487    cxx_predecls = ['#include "base/inet.hh"']
488    swig_predecls = ['class Net::EthAddr;']
489    def __init__(self, value):
490        if value == NextEthernetAddr:
491            self.value = value
492            return
493
494        if not isinstance(value, str):
495            raise TypeError, "expected an ethernet address and didn't get one"
496
497        bytes = value.split(':')
498        if len(bytes) != 6:
499            raise TypeError, 'invalid ethernet address %s' % value
500
501        for byte in bytes:
502            if not 0 <= int(byte) <= 256:
503                raise TypeError, 'invalid ethernet address %s' % value
504
505        self.value = value
506
507    def unproxy(self, base):
508        if self.value == NextEthernetAddr:
509            self.addr = self.value().value
510        return self
511
512    def __str__(self):
513        if self.value == NextEthernetAddr:
514            if hasattr(self, 'addr'):
515                return self.addr
516            else:
517                return "NextEthernetAddr (unresolved)"
518        else:
519            return self.value
520
521time_formats = [ "%a %b %d %H:%M:%S %Z %Y",
522                 "%a %b %d %H:%M:%S %Z %Y",
523                 "%Y/%m/%d %H:%M:%S",
524                 "%Y/%m/%d %H:%M",
525                 "%Y/%m/%d",
526                 "%m/%d/%Y %H:%M:%S",
527                 "%m/%d/%Y %H:%M",
528                 "%m/%d/%Y",
529                 "%m/%d/%y %H:%M:%S",
530                 "%m/%d/%y %H:%M",
531                 "%m/%d/%y"]
532
533
534def parse_time(value):
535    from time import gmtime, strptime, struct_time, time
536    from datetime import datetime, date
537
538    if isinstance(value, struct_time):
539        return value
540
541    if isinstance(value, (int, long)):
542        return gmtime(value)
543
544    if isinstance(value, (datetime, date)):
545        return value.timetuple()
546
547    if isinstance(value, str):
548        if value in ('Now', 'Today'):
549            return time.gmtime(time.time())
550
551        for format in time_formats:
552            try:
553                return strptime(value, format)
554            except ValueError:
555                pass
556
557    raise ValueError, "Could not parse '%s' as a time" % value
558
559class Time(ParamValue):
560    cxx_type = 'time_t'
561    def __init__(self, value):
562        self.value = parse_time(value)
563
564    def __str__(self):
565        tm = self.value
566        return ' '.join([ str(tm[i]) for i in xrange(8)])
567
568    def ini_str(self):
569        return str(self)
570
571# Enumerated types are a little more complex.  The user specifies the
572# type as Enum(foo) where foo is either a list or dictionary of
573# alternatives (typically strings, but not necessarily so).  (In the
574# long run, the integer value of the parameter will be the list index
575# or the corresponding dictionary value.  For now, since we only check
576# that the alternative is valid and then spit it into a .ini file,
577# there's not much point in using the dictionary.)
578
579# What Enum() must do is generate a new type encapsulating the
580# provided list/dictionary so that specific values of the parameter
581# can be instances of that type.  We define two hidden internal
582# classes (_ListEnum and _DictEnum) to serve as base classes, then
583# derive the new type from the appropriate base class on the fly.
584
585
586# Metaclass for Enum types
587class MetaEnum(type):
588    def __init__(cls, name, bases, init_dict):
589        if init_dict.has_key('map'):
590            if not isinstance(cls.map, dict):
591                raise TypeError, "Enum-derived class attribute 'map' " \
592                      "must be of type dict"
593            # build list of value strings from map
594            cls.vals = cls.map.keys()
595            cls.vals.sort()
596        elif init_dict.has_key('vals'):
597            if not isinstance(cls.vals, list):
598                raise TypeError, "Enum-derived class attribute 'vals' " \
599                      "must be of type list"
600            # build string->value map from vals sequence
601            cls.map = {}
602            for idx,val in enumerate(cls.vals):
603                cls.map[val] = idx
604        else:
605            raise TypeError, "Enum-derived class must define "\
606                  "attribute 'map' or 'vals'"
607
608        cls.cxx_type = name + '::Enum'
609
610        super(MetaEnum, cls).__init__(name, bases, init_dict)
611
612    # Generate C++ class declaration for this enum type.
613    # Note that we wrap the enum in a class/struct to act as a namespace,
614    # so that the enum strings can be brief w/o worrying about collisions.
615    def cxx_decl(cls):
616        s = 'struct %s {\n  enum Enum {\n    ' % cls.__name__
617        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
618        s += '\n  };\n};\n'
619        return s
620
621# Base class for enum types.
622class Enum(ParamValue):
623    __metaclass__ = MetaEnum
624    vals = []
625
626    def __init__(self, value):
627        if value not in self.map:
628            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
629                  % (value, self.vals)
630        self.value = value
631
632    def __str__(self):
633        return self.value
634
635ticks_per_sec = None
636
637# how big does a rounding error need to be before we warn about it?
638frequency_tolerance = 0.001  # 0.1%
639
640# convert a floting-point # of ticks to integer, and warn if rounding
641# discards too much precision
642def tick_check(float_ticks):
643    if float_ticks == 0:
644        return 0
645    int_ticks = int(round(float_ticks))
646    err = (float_ticks - int_ticks) / float_ticks
647    if err > frequency_tolerance:
648        print >> sys.stderr, "Warning: rounding error > tolerance"
649        print >> sys.stderr, "    %f rounded to %d" % (float_ticks, int_ticks)
650        #raise ValueError
651    return int_ticks
652
653def getLatency(value):
654    if isinstance(value, Latency) or isinstance(value, Clock):
655        return value.value
656    elif isinstance(value, Frequency) or isinstance(value, RootClock):
657        return 1 / value.value
658    elif isinstance(value, str):
659        try:
660            return convert.toLatency(value)
661        except ValueError:
662            try:
663                return 1 / convert.toFrequency(value)
664            except ValueError:
665                pass # fall through
666    raise ValueError, "Invalid Frequency/Latency value '%s'" % value
667
668
669class Latency(NumericParamValue):
670    cxx_type = 'Tick'
671    cxx_predecls = ['#include "sim/host.hh"']
672    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
673                     '%import "sim/host.hh"']
674    def __init__(self, value):
675        self.value = getLatency(value)
676
677    def __getattr__(self, attr):
678        if attr in ('latency', 'period'):
679            return self
680        if attr == 'frequency':
681            return Frequency(self)
682        raise AttributeError, "Latency object has no attribute '%s'" % attr
683
684    # convert latency to ticks
685    def ini_str(self):
686        return str(tick_check(self.value * ticks_per_sec))
687
688class Frequency(NumericParamValue):
689    cxx_type = 'Tick'
690    cxx_predecls = ['#include "sim/host.hh"']
691    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
692                     '%import "sim/host.hh"']
693    def __init__(self, value):
694        self.value = 1 / getLatency(value)
695
696    def __getattr__(self, attr):
697        if attr == 'frequency':
698            return self
699        if attr in ('latency', 'period'):
700            return Latency(self)
701        raise AttributeError, "Frequency object has no attribute '%s'" % attr
702
703    # convert frequency to ticks per period
704    def ini_str(self):
705        return self.period.ini_str()
706
707# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz).
708# We can't inherit from Frequency because we don't want it to be directly
709# assignable to a regular Frequency parameter.
710class RootClock(ParamValue):
711    cxx_type = 'Tick'
712    cxx_predecls = ['#include "sim/host.hh"']
713    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
714                     '%import "sim/host.hh"']
715    def __init__(self, value):
716        self.value = 1 / getLatency(value)
717
718    def __getattr__(self, attr):
719        if attr == 'frequency':
720            return Frequency(self)
721        if attr in ('latency', 'period'):
722            return Latency(self)
723        raise AttributeError, "Frequency object has no attribute '%s'" % attr
724
725    def ini_str(self):
726        return str(tick_check(self.value))
727
728# A generic frequency and/or Latency value.  Value is stored as a latency,
729# but to avoid ambiguity this object does not support numeric ops (* or /).
730# An explicit conversion to a Latency or Frequency must be made first.
731class Clock(ParamValue):
732    cxx_type = 'Tick'
733    cxx_predecls = ['#include "sim/host.hh"']
734    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
735                     '%import "sim/host.hh"']
736    def __init__(self, value):
737        self.value = getLatency(value)
738
739    def __getattr__(self, attr):
740        if attr == 'frequency':
741            return Frequency(self)
742        if attr in ('latency', 'period'):
743            return Latency(self)
744        raise AttributeError, "Frequency object has no attribute '%s'" % attr
745
746    def ini_str(self):
747        return self.period.ini_str()
748
749class NetworkBandwidth(float,ParamValue):
750    cxx_type = 'float'
751    def __new__(cls, value):
752        val = convert.toNetworkBandwidth(value) / 8.0
753        return super(cls, NetworkBandwidth).__new__(cls, val)
754
755    def __str__(self):
756        return str(self.val)
757
758    def ini_str(self):
759        return '%f' % (ticks_per_sec / float(self))
760
761class MemoryBandwidth(float,ParamValue):
762    cxx_type = 'float'
763    def __new__(self, value):
764        val = convert.toMemoryBandwidth(value)
765        return super(cls, MemoryBandwidth).__new__(cls, val)
766
767    def __str__(self):
768        return str(self.val)
769
770    def ini_str(self):
771        return '%f' % (ticks_per_sec / float(self))
772
773#
774# "Constants"... handy aliases for various values.
775#
776
777# Special class for NULL pointers.  Note the special check in
778# make_param_value() above that lets these be assigned where a
779# SimObject is required.
780# only one copy of a particular node
781class NullSimObject(object):
782    __metaclass__ = Singleton
783
784    def __call__(cls):
785        return cls
786
787    def _instantiate(self, parent = None, path = ''):
788        pass
789
790    def ini_str(self):
791        return 'Null'
792
793    def unproxy(self, base):
794        return self
795
796    def set_path(self, parent, name):
797        pass
798    def __str__(self):
799        return 'Null'
800
801# The only instance you'll ever need...
802NULL = NullSimObject()
803
804def isNullPointer(value):
805    return isinstance(value, NullSimObject)
806
807# Some memory range specifications use this as a default upper bound.
808MaxAddr = Addr.max
809MaxTick = Tick.max
810AllMemory = AddrRange(0, MaxAddr)
811
812
813#####################################################################
814#
815# Port objects
816#
817# Ports are used to interconnect objects in the memory system.
818#
819#####################################################################
820
821# Port reference: encapsulates a reference to a particular port on a
822# particular SimObject.
823class PortRef(object):
824    def __init__(self, simobj, name):
825        assert(isSimObject(simobj) or isSimObjectClass(simobj))
826        self.simobj = simobj
827        self.name = name
828        self.peer = None   # not associated with another port yet
829        self.ccConnected = False # C++ port connection done?
830        self.index = -1  # always -1 for non-vector ports
831
832    def __str__(self):
833        return '%s.%s' % (self.simobj, self.name)
834
835    # for config.ini, print peer's name (not ours)
836    def ini_str(self):
837        return str(self.peer)
838
839    def __getattr__(self, attr):
840        if attr == 'peerObj':
841            # shorthand for proxies
842            return self.peer.simobj
843        raise AttributeError, "'%s' object has no attribute '%s'" % \
844              (self.__class__.__name__, attr)
845
846    # Full connection is symmetric (both ways).  Called via
847    # SimObject.__setattr__ as a result of a port assignment, e.g.,
848    # "obj1.portA = obj2.portB", or via VectorPortElementRef.__setitem__,
849    # e.g., "obj1.portA[3] = obj2.portB".
850    def connect(self, other):
851        if isinstance(other, VectorPortRef):
852            # reference to plain VectorPort is implicit append
853            other = other._get_next()
854        if self.peer and not proxy.isproxy(self.peer):
855            print "warning: overwriting port", self, \
856                  "value", self.peer, "with", other
857        self.peer = other
858        if proxy.isproxy(other):
859            other.set_param_desc(PortParamDesc())
860        elif isinstance(other, PortRef):
861            if other.peer is not self:
862                other.connect(self)
863        else:
864            raise TypeError, \
865                  "assigning non-port reference '%s' to port '%s'" \
866                  % (other, self)
867
868    def clone(self, simobj, memo):
869        if memo.has_key(self):
870            return memo[self]
871        newRef = copy.copy(self)
872        memo[self] = newRef
873        newRef.simobj = simobj
874        assert(isSimObject(newRef.simobj))
875        if self.peer and not proxy.isproxy(self.peer):
876            peerObj = self.peer.simobj(_memo=memo)
877            newRef.peer = self.peer.clone(peerObj, memo)
878            assert(not isinstance(newRef.peer, VectorPortRef))
879        return newRef
880
881    def unproxy(self, simobj):
882        assert(simobj is self.simobj)
883        if proxy.isproxy(self.peer):
884            try:
885                realPeer = self.peer.unproxy(self.simobj)
886            except:
887                print "Error in unproxying port '%s' of %s" % \
888                      (self.name, self.simobj.path())
889                raise
890            self.connect(realPeer)
891
892    # Call C++ to create corresponding port connection between C++ objects
893    def ccConnect(self):
894        if self.ccConnected: # already done this
895            return
896        peer = self.peer
897        internal.main.connectPorts(self.simobj.getCCObject(), self.name,
898                                   self.index, peer.simobj.getCCObject(),
899                                   peer.name, peer.index)
900        self.ccConnected = True
901        peer.ccConnected = True
902
903# A reference to an individual element of a VectorPort... much like a
904# PortRef, but has an index.
905class VectorPortElementRef(PortRef):
906    def __init__(self, simobj, name, index):
907        PortRef.__init__(self, simobj, name)
908        self.index = index
909
910    def __str__(self):
911        return '%s.%s[%d]' % (self.simobj, self.name, self.index)
912
913# A reference to a complete vector-valued port (not just a single element).
914# Can be indexed to retrieve individual VectorPortElementRef instances.
915class VectorPortRef(object):
916    def __init__(self, simobj, name):
917        assert(isSimObject(simobj) or isSimObjectClass(simobj))
918        self.simobj = simobj
919        self.name = name
920        self.elements = []
921
922    def __str__(self):
923        return '%s.%s[:]' % (self.simobj, self.name)
924
925    # for config.ini, print peer's name (not ours)
926    def ini_str(self):
927        return ' '.join([el.ini_str() for el in self.elements])
928
929    def __getitem__(self, key):
930        if not isinstance(key, int):
931            raise TypeError, "VectorPort index must be integer"
932        if key >= len(self.elements):
933            # need to extend list
934            ext = [VectorPortElementRef(self.simobj, self.name, i)
935                   for i in range(len(self.elements), key+1)]
936            self.elements.extend(ext)
937        return self.elements[key]
938
939    def _get_next(self):
940        return self[len(self.elements)]
941
942    def __setitem__(self, key, value):
943        if not isinstance(key, int):
944            raise TypeError, "VectorPort index must be integer"
945        self[key].connect(value)
946
947    def connect(self, other):
948        if isinstance(other, (list, tuple)):
949            # Assign list of port refs to vector port.
950            # For now, append them... not sure if that's the right semantics
951            # or if it should replace the current vector.
952            for ref in other:
953                self._get_next().connect(ref)
954        else:
955            # scalar assignment to plain VectorPort is implicit append
956            self._get_next().connect(other)
957
958    def clone(self, simobj, memo):
959        if memo.has_key(self):
960            return memo[self]
961        newRef = copy.copy(self)
962        memo[self] = newRef
963        newRef.simobj = simobj
964        assert(isSimObject(newRef.simobj))
965        newRef.elements = [el.clone(simobj, memo) for el in self.elements]
966        return newRef
967
968    def unproxy(self, simobj):
969        [el.unproxy(simobj) for el in self.elements]
970
971    def ccConnect(self):
972        [el.ccConnect() for el in self.elements]
973
974# Port description object.  Like a ParamDesc object, this represents a
975# logical port in the SimObject class, not a particular port on a
976# SimObject instance.  The latter are represented by PortRef objects.
977class Port(object):
978    # Port("description") or Port(default, "description")
979    def __init__(self, *args):
980        if len(args) == 1:
981            self.desc = args[0]
982        elif len(args) == 2:
983            self.default = args[0]
984            self.desc = args[1]
985        else:
986            raise TypeError, 'wrong number of arguments'
987        # self.name is set by SimObject class on assignment
988        # e.g., pio_port = Port("blah") sets self.name to 'pio_port'
989
990    # Generate a PortRef for this port on the given SimObject with the
991    # given name
992    def makeRef(self, simobj):
993        return PortRef(simobj, self.name)
994
995    # Connect an instance of this port (on the given SimObject with
996    # the given name) with the port described by the supplied PortRef
997    def connect(self, simobj, ref):
998        self.makeRef(simobj).connect(ref)
999
1000# VectorPort description object.  Like Port, but represents a vector
1001# of connections (e.g., as on a Bus).
1002class VectorPort(Port):
1003    def __init__(self, *args):
1004        Port.__init__(self, *args)
1005        self.isVec = True
1006
1007    def makeRef(self, simobj):
1008        return VectorPortRef(simobj, self.name)
1009
1010# 'Fake' ParamDesc for Port references to assign to the _pdesc slot of
1011# proxy objects (via set_param_desc()) so that proxy error messages
1012# make sense.
1013class PortParamDesc(object):
1014    __metaclass__ = Singleton
1015
1016    ptype_str = 'Port'
1017    ptype = Port
1018
1019
1020__all__ = ['Param', 'VectorParam',
1021           'Enum', 'Bool', 'String', 'Float',
1022           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
1023           'Int32', 'UInt32', 'Int64', 'UInt64',
1024           'Counter', 'Addr', 'Tick', 'Percent',
1025           'TcpPort', 'UdpPort', 'EthernetAddr',
1026           'MemorySize', 'MemorySize32',
1027           'Latency', 'Frequency', 'RootClock', 'Clock',
1028           'NetworkBandwidth', 'MemoryBandwidth',
1029           'Range', 'AddrRange', 'TickRange',
1030           'MaxAddr', 'MaxTick', 'AllMemory',
1031           'Time',
1032           'NextEthernetAddr', 'NULL',
1033           'Port', 'VectorPort']
1034
1035# see comment on imports at end of __init__.py.
1036from SimObject import isSimObject, isSimObjectSequence, isSimObjectClass
1037import proxy
1038import objects
1039import internal
1040