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