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