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