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