params.py revision 3714
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    def __long__(self):
241        return long(self.value)
242
243    def __int__(self):
244        return int(self.value)
245
246    # hook for bounds checking
247    def _check(self):
248        return
249
250    def __mul__(self, other):
251        newobj = self.__class__(self)
252        newobj.value *= other
253        newobj._check()
254        return newobj
255
256    __rmul__ = __mul__
257
258    def __div__(self, other):
259        newobj = self.__class__(self)
260        newobj.value /= other
261        newobj._check()
262        return newobj
263
264    def __sub__(self, other):
265        newobj = self.__class__(self)
266        newobj.value -= other
267        newobj._check()
268        return newobj
269
270# Metaclass for bounds-checked integer parameters.  See CheckedInt.
271class CheckedIntType(type):
272    def __init__(cls, name, bases, dict):
273        super(CheckedIntType, cls).__init__(name, bases, dict)
274
275        # CheckedInt is an abstract base class, so we actually don't
276        # want to do any processing on it... the rest of this code is
277        # just for classes that derive from CheckedInt.
278        if name == 'CheckedInt':
279            return
280
281        if not cls.cxx_predecls:
282            # most derived types require this, so we just do it here once
283            cls.cxx_predecls = ['#include "sim/host.hh"']
284
285        if not cls.swig_predecls:
286            # most derived types require this, so we just do it here once
287            cls.swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
288                                 '%import "sim/host.hh"']
289
290        if not (hasattr(cls, 'min') and hasattr(cls, 'max')):
291            if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')):
292                panic("CheckedInt subclass %s must define either\n" \
293                      "    'min' and 'max' or 'size' and 'unsigned'\n" \
294                      % name);
295            if cls.unsigned:
296                cls.min = 0
297                cls.max = 2 ** cls.size - 1
298            else:
299                cls.min = -(2 ** (cls.size - 1))
300                cls.max = (2 ** (cls.size - 1)) - 1
301
302# Abstract superclass for bounds-checked integer parameters.  This
303# class is subclassed to generate parameter classes with specific
304# bounds.  Initialization of the min and max bounds is done in the
305# metaclass CheckedIntType.__init__.
306class CheckedInt(NumericParamValue):
307    __metaclass__ = CheckedIntType
308
309    def _check(self):
310        if not self.min <= self.value <= self.max:
311            raise TypeError, 'Integer param out of bounds %d < %d < %d' % \
312                  (self.min, self.value, self.max)
313
314    def __init__(self, value):
315        if isinstance(value, str):
316            self.value = convert.toInteger(value)
317        elif isinstance(value, (int, long, float, NumericParamValue)):
318            self.value = long(value)
319        else:
320            raise TypeError, "Can't convert object of type %s to CheckedInt" \
321                  % type(value).__name__
322        self._check()
323
324class Int(CheckedInt):      cxx_type = 'int';      size = 32; unsigned = False
325class Unsigned(CheckedInt): cxx_type = 'unsigned'; size = 32; unsigned = True
326
327class Int8(CheckedInt):     cxx_type =   'int8_t'; size =  8; unsigned = False
328class UInt8(CheckedInt):    cxx_type =  'uint8_t'; size =  8; unsigned = True
329class Int16(CheckedInt):    cxx_type =  'int16_t'; size = 16; unsigned = False
330class UInt16(CheckedInt):   cxx_type = 'uint16_t'; size = 16; unsigned = True
331class Int32(CheckedInt):    cxx_type =  'int32_t'; size = 32; unsigned = False
332class UInt32(CheckedInt):   cxx_type = 'uint32_t'; size = 32; unsigned = True
333class Int64(CheckedInt):    cxx_type =  'int64_t'; size = 64; unsigned = False
334class UInt64(CheckedInt):   cxx_type = 'uint64_t'; size = 64; unsigned = True
335
336class Counter(CheckedInt):  cxx_type = 'Counter';  size = 64; unsigned = True
337class Tick(CheckedInt):     cxx_type = 'Tick';     size = 64; unsigned = True
338class TcpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
339class UdpPort(CheckedInt):  cxx_type = 'uint16_t'; size = 16; unsigned = True
340
341class Percent(CheckedInt):  cxx_type = 'int'; min = 0; max = 100
342
343class Float(ParamValue, float):
344    pass
345
346class MemorySize(CheckedInt):
347    cxx_type = 'uint64_t'
348    size = 64
349    unsigned = True
350    def __init__(self, value):
351        if isinstance(value, MemorySize):
352            self.value = value.value
353        else:
354            self.value = convert.toMemorySize(value)
355        self._check()
356
357class MemorySize32(CheckedInt):
358    size = 32
359    unsigned = True
360    def __init__(self, value):
361        if isinstance(value, MemorySize):
362            self.value = value.value
363        else:
364            self.value = convert.toMemorySize(value)
365        self._check()
366
367class Addr(CheckedInt):
368    cxx_type = 'Addr'
369    cxx_predecls = ['#include "targetarch/isa_traits.hh"']
370    size = 64
371    unsigned = True
372    def __init__(self, value):
373        if isinstance(value, Addr):
374            self.value = value.value
375        else:
376            try:
377                self.value = convert.toMemorySize(value)
378            except TypeError:
379                self.value = long(value)
380        self._check()
381    def __add__(self, other):
382        if isinstance(other, Addr):
383            return self.value + other.value
384        else:
385            return self.value + other
386
387
388class MetaRange(type):
389    def __init__(cls, name, bases, dict):
390        super(MetaRange, cls).__init__(name, bases, dict)
391        if name == 'Range':
392            return
393        cls.cxx_type = 'Range< %s >' % cls.type.cxx_type
394        cls.cxx_predecls = \
395                       ['#include "base/range.hh"'] + cls.type.cxx_predecls
396
397class Range(ParamValue):
398    __metaclass__ = MetaRange
399    type = Int # default; can be overridden in subclasses
400    def __init__(self, *args, **kwargs):
401        def handle_kwargs(self, kwargs):
402            if 'end' in kwargs:
403                self.second = self.type(kwargs.pop('end'))
404            elif 'size' in kwargs:
405                self.second = self.first + self.type(kwargs.pop('size')) - 1
406            else:
407                raise TypeError, "Either end or size must be specified"
408
409        if len(args) == 0:
410            self.first = self.type(kwargs.pop('start'))
411            handle_kwargs(self, kwargs)
412
413        elif len(args) == 1:
414            if kwargs:
415                self.first = self.type(args[0])
416                handle_kwargs(self, kwargs)
417            elif isinstance(args[0], Range):
418                self.first = self.type(args[0].first)
419                self.second = self.type(args[0].second)
420            else:
421                self.first = self.type(0)
422                self.second = self.type(args[0]) - 1
423
424        elif len(args) == 2:
425            self.first = self.type(args[0])
426            self.second = self.type(args[1])
427        else:
428            raise TypeError, "Too many arguments specified"
429
430        if kwargs:
431            raise TypeError, "too many keywords: %s" % kwargs.keys()
432
433    def __str__(self):
434        return '%s:%s' % (self.first, self.second)
435
436class AddrRange(Range):
437    type = Addr
438
439class TickRange(Range):
440    type = Tick
441
442# Boolean parameter type.  Python doesn't let you subclass bool, since
443# it doesn't want to let you create multiple instances of True and
444# False.  Thus this is a little more complicated than String.
445class Bool(ParamValue):
446    cxx_type = 'bool'
447    def __init__(self, value):
448        try:
449            self.value = convert.toBool(value)
450        except TypeError:
451            self.value = bool(value)
452
453    def __str__(self):
454        return str(self.value)
455
456    def ini_str(self):
457        if self.value:
458            return 'true'
459        return 'false'
460
461def IncEthernetAddr(addr, val = 1):
462    bytes = map(lambda x: int(x, 16), addr.split(':'))
463    bytes[5] += val
464    for i in (5, 4, 3, 2, 1):
465        val,rem = divmod(bytes[i], 256)
466        bytes[i] = rem
467        if val == 0:
468            break
469        bytes[i - 1] += val
470    assert(bytes[0] <= 255)
471    return ':'.join(map(lambda x: '%02x' % x, bytes))
472
473class NextEthernetAddr(object):
474    addr = "00:90:00:00:00:01"
475
476    def __init__(self, inc = 1):
477        self.value = NextEthernetAddr.addr
478        NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc)
479
480class EthernetAddr(ParamValue):
481    cxx_type = 'Net::EthAddr'
482    cxx_predecls = ['#include "base/inet.hh"']
483    swig_predecls = ['class Net::EthAddr;']
484    def __init__(self, value):
485        if value == NextEthernetAddr:
486            self.value = value
487            return
488
489        if not isinstance(value, str):
490            raise TypeError, "expected an ethernet address and didn't get one"
491
492        bytes = value.split(':')
493        if len(bytes) != 6:
494            raise TypeError, 'invalid ethernet address %s' % value
495
496        for byte in bytes:
497            if not 0 <= int(byte) <= 256:
498                raise TypeError, 'invalid ethernet address %s' % value
499
500        self.value = value
501
502    def unproxy(self, base):
503        if self.value == NextEthernetAddr:
504            self.addr = self.value().value
505        return self
506
507    def __str__(self):
508        if self.value == NextEthernetAddr:
509            if hasattr(self, 'addr'):
510                return self.addr
511            else:
512                return "NextEthernetAddr (unresolved)"
513        else:
514            return self.value
515
516# Enumerated types are a little more complex.  The user specifies the
517# type as Enum(foo) where foo is either a list or dictionary of
518# alternatives (typically strings, but not necessarily so).  (In the
519# long run, the integer value of the parameter will be the list index
520# or the corresponding dictionary value.  For now, since we only check
521# that the alternative is valid and then spit it into a .ini file,
522# there's not much point in using the dictionary.)
523
524# What Enum() must do is generate a new type encapsulating the
525# provided list/dictionary so that specific values of the parameter
526# can be instances of that type.  We define two hidden internal
527# classes (_ListEnum and _DictEnum) to serve as base classes, then
528# derive the new type from the appropriate base class on the fly.
529
530
531# Metaclass for Enum types
532class MetaEnum(type):
533    def __init__(cls, name, bases, init_dict):
534        if init_dict.has_key('map'):
535            if not isinstance(cls.map, dict):
536                raise TypeError, "Enum-derived class attribute 'map' " \
537                      "must be of type dict"
538            # build list of value strings from map
539            cls.vals = cls.map.keys()
540            cls.vals.sort()
541        elif init_dict.has_key('vals'):
542            if not isinstance(cls.vals, list):
543                raise TypeError, "Enum-derived class attribute 'vals' " \
544                      "must be of type list"
545            # build string->value map from vals sequence
546            cls.map = {}
547            for idx,val in enumerate(cls.vals):
548                cls.map[val] = idx
549        else:
550            raise TypeError, "Enum-derived class must define "\
551                  "attribute 'map' or 'vals'"
552
553        cls.cxx_type = name + '::Enum'
554
555        super(MetaEnum, cls).__init__(name, bases, init_dict)
556
557    # Generate C++ class declaration for this enum type.
558    # Note that we wrap the enum in a class/struct to act as a namespace,
559    # so that the enum strings can be brief w/o worrying about collisions.
560    def cxx_decl(cls):
561        s = 'struct %s {\n  enum Enum {\n    ' % cls.__name__
562        s += ',\n    '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals])
563        s += '\n  };\n};\n'
564        return s
565
566# Base class for enum types.
567class Enum(ParamValue):
568    __metaclass__ = MetaEnum
569    vals = []
570
571    def __init__(self, value):
572        if value not in self.map:
573            raise TypeError, "Enum param got bad value '%s' (not in %s)" \
574                  % (value, self.vals)
575        self.value = value
576
577    def __str__(self):
578        return self.value
579
580ticks_per_sec = None
581
582# how big does a rounding error need to be before we warn about it?
583frequency_tolerance = 0.001  # 0.1%
584
585# convert a floting-point # of ticks to integer, and warn if rounding
586# discards too much precision
587def tick_check(float_ticks):
588    if float_ticks == 0:
589        return 0
590    int_ticks = int(round(float_ticks))
591    err = (float_ticks - int_ticks) / float_ticks
592    if err > frequency_tolerance:
593        print >> sys.stderr, "Warning: rounding error > tolerance"
594        print >> sys.stderr, "    %f rounded to %d" % (float_ticks, int_ticks)
595        #raise ValueError
596    return int_ticks
597
598def getLatency(value):
599    if isinstance(value, Latency) or isinstance(value, Clock):
600        return value.value
601    elif isinstance(value, Frequency) or isinstance(value, RootClock):
602        return 1 / value.value
603    elif isinstance(value, str):
604        try:
605            return convert.toLatency(value)
606        except ValueError:
607            try:
608                return 1 / convert.toFrequency(value)
609            except ValueError:
610                pass # fall through
611    raise ValueError, "Invalid Frequency/Latency value '%s'" % value
612
613
614class Latency(NumericParamValue):
615    cxx_type = 'Tick'
616    cxx_predecls = ['#include "sim/host.hh"']
617    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
618                     '%import "sim/host.hh"']
619    def __init__(self, value):
620        self.value = getLatency(value)
621
622    def __getattr__(self, attr):
623        if attr in ('latency', 'period'):
624            return self
625        if attr == 'frequency':
626            return Frequency(self)
627        raise AttributeError, "Latency object has no attribute '%s'" % attr
628
629    # convert latency to ticks
630    def ini_str(self):
631        return str(tick_check(self.value * ticks_per_sec))
632
633class Frequency(NumericParamValue):
634    cxx_type = 'Tick'
635    cxx_predecls = ['#include "sim/host.hh"']
636    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
637                     '%import "sim/host.hh"']
638    def __init__(self, value):
639        self.value = 1 / getLatency(value)
640
641    def __getattr__(self, attr):
642        if attr == 'frequency':
643            return self
644        if attr in ('latency', 'period'):
645            return Latency(self)
646        raise AttributeError, "Frequency object has no attribute '%s'" % attr
647
648    # convert frequency to ticks per period
649    def ini_str(self):
650        return self.period.ini_str()
651
652# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz).
653# We can't inherit from Frequency because we don't want it to be directly
654# assignable to a regular Frequency parameter.
655class RootClock(ParamValue):
656    cxx_type = 'Tick'
657    cxx_predecls = ['#include "sim/host.hh"']
658    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
659                     '%import "sim/host.hh"']
660    def __init__(self, value):
661        self.value = 1 / getLatency(value)
662
663    def __getattr__(self, attr):
664        if attr == 'frequency':
665            return Frequency(self)
666        if attr in ('latency', 'period'):
667            return Latency(self)
668        raise AttributeError, "Frequency object has no attribute '%s'" % attr
669
670    def ini_str(self):
671        return str(tick_check(self.value))
672
673# A generic frequency and/or Latency value.  Value is stored as a latency,
674# but to avoid ambiguity this object does not support numeric ops (* or /).
675# An explicit conversion to a Latency or Frequency must be made first.
676class Clock(ParamValue):
677    cxx_type = 'Tick'
678    cxx_predecls = ['#include "sim/host.hh"']
679    swig_predecls = ['%import "python/m5/swig/stdint.i"\n' +
680                     '%import "sim/host.hh"']
681    def __init__(self, value):
682        self.value = getLatency(value)
683
684    def __getattr__(self, attr):
685        if attr == 'frequency':
686            return Frequency(self)
687        if attr in ('latency', 'period'):
688            return Latency(self)
689        raise AttributeError, "Frequency object has no attribute '%s'" % attr
690
691    def ini_str(self):
692        return self.period.ini_str()
693
694class NetworkBandwidth(float,ParamValue):
695    cxx_type = 'float'
696    def __new__(cls, value):
697        val = convert.toNetworkBandwidth(value) / 8.0
698        return super(cls, NetworkBandwidth).__new__(cls, val)
699
700    def __str__(self):
701        return str(self.val)
702
703    def ini_str(self):
704        return '%f' % (ticks_per_sec / float(self))
705
706class MemoryBandwidth(float,ParamValue):
707    cxx_type = 'float'
708    def __new__(self, value):
709        val = convert.toMemoryBandwidth(value)
710        return super(cls, MemoryBandwidth).__new__(cls, val)
711
712    def __str__(self):
713        return str(self.val)
714
715    def ini_str(self):
716        return '%f' % (ticks_per_sec / float(self))
717
718#
719# "Constants"... handy aliases for various values.
720#
721
722# Special class for NULL pointers.  Note the special check in
723# make_param_value() above that lets these be assigned where a
724# SimObject is required.
725# only one copy of a particular node
726class NullSimObject(object):
727    __metaclass__ = Singleton
728
729    def __call__(cls):
730        return cls
731
732    def _instantiate(self, parent = None, path = ''):
733        pass
734
735    def ini_str(self):
736        return 'Null'
737
738    def unproxy(self, base):
739        return self
740
741    def set_path(self, parent, name):
742        pass
743    def __str__(self):
744        return 'Null'
745
746# The only instance you'll ever need...
747NULL = NullSimObject()
748
749def isNullPointer(value):
750    return isinstance(value, NullSimObject)
751
752# Some memory range specifications use this as a default upper bound.
753MaxAddr = Addr.max
754MaxTick = Tick.max
755AllMemory = AddrRange(0, MaxAddr)
756
757
758#####################################################################
759#
760# Port objects
761#
762# Ports are used to interconnect objects in the memory system.
763#
764#####################################################################
765
766# Port reference: encapsulates a reference to a particular port on a
767# particular SimObject.
768class PortRef(object):
769    def __init__(self, simobj, name):
770        assert(isSimObject(simobj) or isSimObjectClass(simobj))
771        self.simobj = simobj
772        self.name = name
773        self.peer = None   # not associated with another port yet
774        self.ccConnected = False # C++ port connection done?
775        self.index = -1  # always -1 for non-vector ports
776
777    def __str__(self):
778        return '%s.%s' % (self.simobj, self.name)
779
780    # for config.ini, print peer's name (not ours)
781    def ini_str(self):
782        return str(self.peer)
783
784    def __getattr__(self, attr):
785        if attr == 'peerObj':
786            # shorthand for proxies
787            return self.peer.simobj
788        raise AttributeError, "'%s' object has no attribute '%s'" % \
789              (self.__class__.__name__, attr)
790
791    # Full connection is symmetric (both ways).  Called via
792    # SimObject.__setattr__ as a result of a port assignment, e.g.,
793    # "obj1.portA = obj2.portB", or via VectorPortElementRef.__setitem__,
794    # e.g., "obj1.portA[3] = obj2.portB".
795    def connect(self, other):
796        if isinstance(other, VectorPortRef):
797            # reference to plain VectorPort is implicit append
798            other = other._get_next()
799        if self.peer and not proxy.isproxy(self.peer):
800            print "warning: overwriting port", self, \
801                  "value", self.peer, "with", other
802        self.peer = other
803        if proxy.isproxy(other):
804            other.set_param_desc(PortParamDesc())
805        elif isinstance(other, PortRef):
806            if other.peer is not self:
807                other.connect(self)
808        else:
809            raise TypeError, \
810                  "assigning non-port reference '%s' to port '%s'" \
811                  % (other, self)
812
813    def clone(self, simobj, memo):
814        if memo.has_key(self):
815            return memo[self]
816        newRef = copy.copy(self)
817        memo[self] = newRef
818        newRef.simobj = simobj
819        assert(isSimObject(newRef.simobj))
820        if self.peer and not proxy.isproxy(self.peer):
821            peerObj = self.peer.simobj(_memo=memo)
822            newRef.peer = self.peer.clone(peerObj, memo)
823            assert(not isinstance(newRef.peer, VectorPortRef))
824        return newRef
825
826    def unproxy(self, simobj):
827        assert(simobj is self.simobj)
828        if proxy.isproxy(self.peer):
829            try:
830                realPeer = self.peer.unproxy(self.simobj)
831            except:
832                print "Error in unproxying port '%s' of %s" % \
833                      (self.name, self.simobj.path())
834                raise
835            self.connect(realPeer)
836
837    # Call C++ to create corresponding port connection between C++ objects
838    def ccConnect(self):
839        if self.ccConnected: # already done this
840            return
841        peer = self.peer
842        internal.main.connectPorts(self.simobj.getCCObject(), self.name,
843                                   self.index, peer.simobj.getCCObject(),
844                                   peer.name, peer.index)
845        self.ccConnected = True
846        peer.ccConnected = True
847
848# A reference to an individual element of a VectorPort... much like a
849# PortRef, but has an index.
850class VectorPortElementRef(PortRef):
851    def __init__(self, simobj, name, index):
852        PortRef.__init__(self, simobj, name)
853        self.index = index
854
855    def __str__(self):
856        return '%s.%s[%d]' % (self.simobj, self.name, self.index)
857
858# A reference to a complete vector-valued port (not just a single element).
859# Can be indexed to retrieve individual VectorPortElementRef instances.
860class VectorPortRef(object):
861    def __init__(self, simobj, name):
862        assert(isSimObject(simobj) or isSimObjectClass(simobj))
863        self.simobj = simobj
864        self.name = name
865        self.elements = []
866
867    def __str__(self):
868        return '%s.%s[:]' % (self.simobj, self.name)
869
870    # for config.ini, print peer's name (not ours)
871    def ini_str(self):
872        return ' '.join([el.ini_str() for el in self.elements])
873
874    def __getitem__(self, key):
875        if not isinstance(key, int):
876            raise TypeError, "VectorPort index must be integer"
877        if key >= len(self.elements):
878            # need to extend list
879            ext = [VectorPortElementRef(self.simobj, self.name, i)
880                   for i in range(len(self.elements), key+1)]
881            self.elements.extend(ext)
882        return self.elements[key]
883
884    def _get_next(self):
885        return self[len(self.elements)]
886
887    def __setitem__(self, key, value):
888        if not isinstance(key, int):
889            raise TypeError, "VectorPort index must be integer"
890        self[key].connect(value)
891
892    def connect(self, other):
893        if isinstance(other, (list, tuple)):
894            # Assign list of port refs to vector port.
895            # For now, append them... not sure if that's the right semantics
896            # or if it should replace the current vector.
897            for ref in other:
898                self._get_next().connect(ref)
899        else:
900            # scalar assignment to plain VectorPort is implicit append
901            self._get_next().connect(other)
902
903    def clone(self, simobj, memo):
904        if memo.has_key(self):
905            return memo[self]
906        newRef = copy.copy(self)
907        memo[self] = newRef
908        newRef.simobj = simobj
909        assert(isSimObject(newRef.simobj))
910        newRef.elements = [el.clone(simobj, memo) for el in self.elements]
911        return newRef
912
913    def unproxy(self, simobj):
914        [el.unproxy(simobj) for el in self.elements]
915
916    def ccConnect(self):
917        [el.ccConnect() for el in self.elements]
918
919# Port description object.  Like a ParamDesc object, this represents a
920# logical port in the SimObject class, not a particular port on a
921# SimObject instance.  The latter are represented by PortRef objects.
922class Port(object):
923    # Port("description") or Port(default, "description")
924    def __init__(self, *args):
925        if len(args) == 1:
926            self.desc = args[0]
927        elif len(args) == 2:
928            self.default = args[0]
929            self.desc = args[1]
930        else:
931            raise TypeError, 'wrong number of arguments'
932        # self.name is set by SimObject class on assignment
933        # e.g., pio_port = Port("blah") sets self.name to 'pio_port'
934
935    # Generate a PortRef for this port on the given SimObject with the
936    # given name
937    def makeRef(self, simobj):
938        return PortRef(simobj, self.name)
939
940    # Connect an instance of this port (on the given SimObject with
941    # the given name) with the port described by the supplied PortRef
942    def connect(self, simobj, ref):
943        self.makeRef(simobj).connect(ref)
944
945# VectorPort description object.  Like Port, but represents a vector
946# of connections (e.g., as on a Bus).
947class VectorPort(Port):
948    def __init__(self, *args):
949        Port.__init__(self, *args)
950        self.isVec = True
951
952    def makeRef(self, simobj):
953        return VectorPortRef(simobj, self.name)
954
955# 'Fake' ParamDesc for Port references to assign to the _pdesc slot of
956# proxy objects (via set_param_desc()) so that proxy error messages
957# make sense.
958class PortParamDesc(object):
959    __metaclass__ = Singleton
960
961    ptype_str = 'Port'
962    ptype = Port
963
964
965__all__ = ['Param', 'VectorParam',
966           'Enum', 'Bool', 'String', 'Float',
967           'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
968           'Int32', 'UInt32', 'Int64', 'UInt64',
969           'Counter', 'Addr', 'Tick', 'Percent',
970           'TcpPort', 'UdpPort', 'EthernetAddr',
971           'MemorySize', 'MemorySize32',
972           'Latency', 'Frequency', 'RootClock', 'Clock',
973           'NetworkBandwidth', 'MemoryBandwidth',
974           'Range', 'AddrRange', 'TickRange',
975           'MaxAddr', 'MaxTick', 'AllMemory',
976           'NextEthernetAddr', 'NULL',
977           'Port', 'VectorPort']
978
979# see comment on imports at end of __init__.py.
980from SimObject import isSimObject, isSimObjectSequence, isSimObjectClass
981import proxy
982import objects
983import internal
984