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