SimObject.py revision 1815
1# Copyright (c) 2004-2005 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 27from __future__ import generators 28import os, re, sys, types, inspect 29 30import m5 31panic = m5.panic 32from convert import * 33from multidict import multidict 34 35noDot = False 36try: 37 import pydot 38except: 39 noDot = True 40 41class Singleton(type): 42 def __call__(cls, *args, **kwargs): 43 if hasattr(cls, '_instance'): 44 return cls._instance 45 46 cls._instance = super(Singleton, cls).__call__(*args, **kwargs) 47 return cls._instance 48 49##################################################################### 50# 51# M5 Python Configuration Utility 52# 53# The basic idea is to write simple Python programs that build Python 54# objects corresponding to M5 SimObjects for the desired simulation 55# configuration. For now, the Python emits a .ini file that can be 56# parsed by M5. In the future, some tighter integration between M5 57# and the Python interpreter may allow bypassing the .ini file. 58# 59# Each SimObject class in M5 is represented by a Python class with the 60# same name. The Python inheritance tree mirrors the M5 C++ tree 61# (e.g., SimpleCPU derives from BaseCPU in both cases, and all 62# SimObjects inherit from a single SimObject base class). To specify 63# an instance of an M5 SimObject in a configuration, the user simply 64# instantiates the corresponding Python object. The parameters for 65# that SimObject are given by assigning to attributes of the Python 66# object, either using keyword assignment in the constructor or in 67# separate assignment statements. For example: 68# 69# cache = BaseCache(size='64KB') 70# cache.hit_latency = 3 71# cache.assoc = 8 72# 73# The magic lies in the mapping of the Python attributes for SimObject 74# classes to the actual SimObject parameter specifications. This 75# allows parameter validity checking in the Python code. Continuing 76# the example above, the statements "cache.blurfl=3" or 77# "cache.assoc='hello'" would both result in runtime errors in Python, 78# since the BaseCache object has no 'blurfl' parameter and the 'assoc' 79# parameter requires an integer, respectively. This magic is done 80# primarily by overriding the special __setattr__ method that controls 81# assignment to object attributes. 82# 83# Once a set of Python objects have been instantiated in a hierarchy, 84# calling 'instantiate(obj)' (where obj is the root of the hierarchy) 85# will generate a .ini file. See simple-4cpu.py for an example 86# (corresponding to m5-test/simple-4cpu.ini). 87# 88##################################################################### 89 90##################################################################### 91# 92# ConfigNode/SimObject classes 93# 94# The Python class hierarchy rooted by ConfigNode (which is the base 95# class of SimObject, which in turn is the base class of all other M5 96# SimObject classes) has special attribute behavior. In general, an 97# object in this hierarchy has three categories of attribute-like 98# things: 99# 100# 1. Regular Python methods and variables. These must start with an 101# underscore to be treated normally. 102# 103# 2. SimObject parameters. These values are stored as normal Python 104# attributes, but all assignments to these attributes are checked 105# against the pre-defined set of parameters stored in the class's 106# _params dictionary. Assignments to attributes that do not 107# correspond to predefined parameters, or that are not of the correct 108# type, incur runtime errors. 109# 110# 3. Hierarchy children. The child nodes of a ConfigNode are stored 111# in the node's _children dictionary, but can be accessed using the 112# Python attribute dot-notation (just as they are printed out by the 113# simulator). Children cannot be created using attribute assigment; 114# they must be added by specifying the parent node in the child's 115# constructor or using the '+=' operator. 116 117# The SimObject parameters are the most complex, for a few reasons. 118# First, both parameter descriptions and parameter values are 119# inherited. Thus parameter description lookup must go up the 120# inheritance chain like normal attribute lookup, but this behavior 121# must be explicitly coded since the lookup occurs in each class's 122# _params attribute. Second, because parameter values can be set 123# on SimObject classes (to implement default values), the parameter 124# checking behavior must be enforced on class attribute assignments as 125# well as instance attribute assignments. Finally, because we allow 126# class specialization via inheritance (e.g., see the L1Cache class in 127# the simple-4cpu.py example), we must do parameter checking even on 128# class instantiation. To provide all these features, we use a 129# metaclass to define most of the SimObject parameter behavior for 130# this class hierarchy. 131# 132##################################################################### 133 134def isSimObject(value): 135 return isinstance(value, SimObject) 136 137def isSimObjSequence(value): 138 if not isinstance(value, (list, tuple)): 139 return False 140 141 for val in value: 142 if not isNullPointer(val) and not isSimObject(val): 143 return False 144 145 return True 146 147def isNullPointer(value): 148 return isinstance(value, NullSimObject) 149 150# The metaclass for ConfigNode (and thus for everything that derives 151# from ConfigNode, including SimObject). This class controls how new 152# classes that derive from ConfigNode are instantiated, and provides 153# inherited class behavior (just like a class controls how instances 154# of that class are instantiated, and provides inherited instance 155# behavior). 156class MetaSimObject(type): 157 # Attributes that can be set only at initialization time 158 init_keywords = { 'abstract' : types.BooleanType, 159 'type' : types.StringType } 160 # Attributes that can be set any time 161 keywords = { 'check' : types.FunctionType, 162 'children' : types.ListType } 163 164 # __new__ is called before __init__, and is where the statements 165 # in the body of the class definition get loaded into the class's 166 # __dict__. We intercept this to filter out parameter assignments 167 # and only allow "private" attributes to be passed to the base 168 # __new__ (starting with underscore). 169 def __new__(mcls, name, bases, dict): 170 # Copy "private" attributes (including special methods such as __new__) 171 # to the official dict. Everything else goes in _init_dict to be 172 # filtered in __init__. 173 cls_dict = {} 174 for key,val in dict.items(): 175 if key.startswith('_'): 176 cls_dict[key] = val 177 del dict[key] 178 cls_dict['_init_dict'] = dict 179 return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict) 180 181 # initialization 182 def __init__(cls, name, bases, dict): 183 super(MetaSimObject, cls).__init__(name, bases, dict) 184 185 # initialize required attributes 186 cls._params = multidict() 187 cls._values = multidict() 188 cls._anon_subclass_counter = 0 189 190 # We don't support multiple inheritance. If you want to, you 191 # must fix multidict to deal with it properly. 192 if len(bases) > 1: 193 raise TypeError, "SimObjects do not support multiple inheritance" 194 195 base = bases[0] 196 197 if isinstance(base, MetaSimObject): 198 cls._params.parent = base._params 199 cls._values.parent = base._values 200 201 # If your parent has a value in it that's a config node, clone 202 # it. Do this now so if we update any of the values' 203 # attributes we are updating the clone and not the original. 204 for key,val in base._values.iteritems(): 205 206 # don't clone if (1) we're about to overwrite it with 207 # a local setting or (2) we've already cloned a copy 208 # from an earlier (more derived) base 209 if cls._init_dict.has_key(key) or cls._values.has_key(key): 210 continue 211 212 if isSimObject(val): 213 cls._values[key] = val() 214 elif isSimObjSequence(val) and len(val): 215 cls._values[key] = [ v() for v in val ] 216 217 # now process remaining _init_dict items 218 for key,val in cls._init_dict.items(): 219 if isinstance(val, (types.FunctionType, types.TypeType)): 220 type.__setattr__(cls, key, val) 221 222 # param descriptions 223 elif isinstance(val, ParamDesc): 224 cls._new_param(key, val) 225 226 # init-time-only keywords 227 elif cls.init_keywords.has_key(key): 228 cls._set_keyword(key, val, cls.init_keywords[key]) 229 230 # default: use normal path (ends up in __setattr__) 231 else: 232 setattr(cls, key, val) 233 234 def _set_keyword(cls, keyword, val, kwtype): 235 if not isinstance(val, kwtype): 236 raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \ 237 (keyword, type(val), kwtype) 238 if isinstance(val, types.FunctionType): 239 val = classmethod(val) 240 type.__setattr__(cls, keyword, val) 241 242 def _new_param(cls, name, value): 243 cls._params[name] = value 244 if hasattr(value, 'default'): 245 setattr(cls, name, value.default) 246 247 # Set attribute (called on foo.attr = value when foo is an 248 # instance of class cls). 249 def __setattr__(cls, attr, value): 250 # normal processing for private attributes 251 if attr.startswith('_'): 252 type.__setattr__(cls, attr, value) 253 return 254 255 if cls.keywords.has_key(attr): 256 cls._set_keyword(attr, value, cls.keywords[attr]) 257 return 258 259 # must be SimObject param 260 param = cls._params.get(attr, None) 261 if param: 262 # It's ok: set attribute by delegating to 'object' class. 263 try: 264 cls._values[attr] = param.convert(value) 265 except Exception, e: 266 msg = "%s\nError setting param %s.%s to %s\n" % \ 267 (e, cls.__name__, attr, value) 268 e.args = (msg, ) 269 raise 270 # I would love to get rid of this 271 elif isSimObject(value) or isSimObjSequence(value): 272 cls._values[attr] = value 273 else: 274 raise AttributeError, \ 275 "Class %s has no parameter %s" % (cls.__name__, attr) 276 277 def __getattr__(cls, attr): 278 if cls._values.has_key(attr): 279 return cls._values[attr] 280 281 raise AttributeError, \ 282 "object '%s' has no attribute '%s'" % (cls.__name__, attr) 283 284# The ConfigNode class is the root of the special hierarchy. Most of 285# the code in this class deals with the configuration hierarchy itself 286# (parent/child node relationships). 287class SimObject(object): 288 # Specify metaclass. Any class inheriting from SimObject will 289 # get this metaclass. 290 __metaclass__ = MetaSimObject 291 292 def __init__(self, _value_parent = None, **kwargs): 293 self._children = {} 294 if _value_parent and type(_value_parent) != type(self): 295 # this was called as a type conversion rather than a clone 296 raise TypeError, "Cannot convert %s to %s" % \ 297 (_value_parent.__class__.__name__, self.__class__.__name__) 298 if not _value_parent: 299 _value_parent = self.__class__ 300 # clone values 301 self._values = multidict(_value_parent._values) 302 for key,val in _value_parent._values.iteritems(): 303 if isSimObject(val): 304 setattr(self, key, val()) 305 elif isSimObjSequence(val) and len(val): 306 setattr(self, key, [ v() for v in val ]) 307 # apply attribute assignments from keyword args, if any 308 for key,val in kwargs.iteritems(): 309 setattr(self, key, val) 310 311 def __call__(self, **kwargs): 312 return self.__class__(_value_parent = self, **kwargs) 313 314 def __getattr__(self, attr): 315 if self._values.has_key(attr): 316 return self._values[attr] 317 318 raise AttributeError, "object '%s' has no attribute '%s'" \ 319 % (self.__class__.__name__, attr) 320 321 # Set attribute (called on foo.attr = value when foo is an 322 # instance of class cls). 323 def __setattr__(self, attr, value): 324 # normal processing for private attributes 325 if attr.startswith('_'): 326 object.__setattr__(self, attr, value) 327 return 328 329 # must be SimObject param 330 param = self._params.get(attr, None) 331 if param: 332 # It's ok: set attribute by delegating to 'object' class. 333 try: 334 value = param.convert(value) 335 except Exception, e: 336 msg = "%s\nError setting param %s.%s to %s\n" % \ 337 (e, self.__class__.__name__, attr, value) 338 e.args = (msg, ) 339 raise 340 # I would love to get rid of this 341 elif isSimObject(value) or isSimObjSequence(value): 342 pass 343 else: 344 raise AttributeError, "Class %s has no parameter %s" \ 345 % (self.__class__.__name__, attr) 346 347 # clear out old child with this name, if any 348 self.clear_child(attr) 349 350 if isSimObject(value): 351 value.set_path(self, attr) 352 elif isSimObjSequence(value): 353 value = SimObjVector(value) 354 [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)] 355 356 self._values[attr] = value 357 358 # this hack allows tacking a '[0]' onto parameters that may or may 359 # not be vectors, and always getting the first element (e.g. cpus) 360 def __getitem__(self, key): 361 if key == 0: 362 return self 363 raise TypeError, "Non-zero index '%s' to SimObject" % key 364 365 # clear out children with given name, even if it's a vector 366 def clear_child(self, name): 367 if not self._children.has_key(name): 368 return 369 child = self._children[name] 370 if isinstance(child, SimObjVector): 371 for i in xrange(len(child)): 372 del self._children["s%d" % (name, i)] 373 del self._children[name] 374 375 def add_child(self, name, value): 376 self._children[name] = value 377 378 def set_path(self, parent, name): 379 if not hasattr(self, '_parent'): 380 self._parent = parent 381 self._name = name 382 parent.add_child(name, self) 383 384 def path(self): 385 if not hasattr(self, '_parent'): 386 return 'root' 387 ppath = self._parent.path() 388 if ppath == 'root': 389 return self._name 390 return ppath + "." + self._name 391 392 def __str__(self): 393 return self.path() 394 395 def ini_str(self): 396 return self.path() 397 398 def find_any(self, ptype): 399 if isinstance(self, ptype): 400 return self, True 401 402 found_obj = None 403 for child in self._children.itervalues(): 404 if isinstance(child, ptype): 405 if found_obj != None and child != found_obj: 406 raise AttributeError, \ 407 'parent.any matched more than one: %s %s' % \ 408 (found_obj.path, child.path) 409 found_obj = child 410 # search param space 411 for pname,pdesc in self._params.iteritems(): 412 if issubclass(pdesc.ptype, ptype): 413 match_obj = self._values[pname] 414 if found_obj != None and found_obj != match_obj: 415 raise AttributeError, \ 416 'parent.any matched more than one: %s' % obj.path 417 found_obj = match_obj 418 return found_obj, found_obj != None 419 420 def unproxy(self, base): 421 return self 422 423 def print_ini(self): 424 print '[' + self.path() + ']' # .ini section header 425 426 if hasattr(self, 'type') and not isinstance(self, ParamContext): 427 print 'type=%s' % self.type 428 429 child_names = self._children.keys() 430 child_names.sort() 431 np_child_names = [c for c in child_names \ 432 if not isinstance(self._children[c], ParamContext)] 433 if len(np_child_names): 434 print 'children=%s' % ' '.join(np_child_names) 435 436 param_names = self._params.keys() 437 param_names.sort() 438 for param in param_names: 439 value = self._values.get(param, None) 440 if value != None: 441 if isproxy(value): 442 try: 443 value = value.unproxy(self) 444 except: 445 print >> sys.stderr, \ 446 "Error in unproxying param '%s' of %s" % \ 447 (param, self.path()) 448 raise 449 setattr(self, param, value) 450 print '%s=%s' % (param, self._values[param].ini_str()) 451 452 print # blank line between objects 453 454 for child in child_names: 455 self._children[child].print_ini() 456 457 # generate output file for 'dot' to display as a pretty graph. 458 # this code is currently broken. 459 def outputDot(self, dot): 460 label = "{%s|" % self.path 461 if isSimObject(self.realtype): 462 label += '%s|' % self.type 463 464 if self.children: 465 # instantiate children in same order they were added for 466 # backward compatibility (else we can end up with cpu1 467 # before cpu0). 468 for c in self.children: 469 dot.add_edge(pydot.Edge(self.path,c.path, style="bold")) 470 471 simobjs = [] 472 for param in self.params: 473 try: 474 if param.value is None: 475 raise AttributeError, 'Parameter with no value' 476 477 value = param.value 478 string = param.string(value) 479 except Exception, e: 480 msg = 'exception in %s:%s\n%s' % (self.name, param.name, e) 481 e.args = (msg, ) 482 raise 483 484 if isSimObject(param.ptype) and string != "Null": 485 simobjs.append(string) 486 else: 487 label += '%s = %s\\n' % (param.name, string) 488 489 for so in simobjs: 490 label += "|<%s> %s" % (so, so) 491 dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, 492 tailport="w")) 493 label += '}' 494 dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label)) 495 496 # recursively dump out children 497 for c in self.children: 498 c.outputDot(dot) 499 500class ParamContext(SimObject): 501 pass 502 503##################################################################### 504# 505# Proxy object support. 506# 507##################################################################### 508 509class BaseProxy(object): 510 def __init__(self, search_self, search_up): 511 self._search_self = search_self 512 self._search_up = search_up 513 self._multiplier = None 514 515 def __setattr__(self, attr, value): 516 if not attr.startswith('_'): 517 raise AttributeError, 'cannot set attribute on proxy object' 518 super(BaseProxy, self).__setattr__(attr, value) 519 520 # support multiplying proxies by constants 521 def __mul__(self, other): 522 if not isinstance(other, (int, float)): 523 raise TypeError, "Proxy multiplier must be integer" 524 if self._multiplier == None: 525 self._multiplier = other 526 else: 527 # support chained multipliers 528 self._multiplier *= other 529 return self 530 531 __rmul__ = __mul__ 532 533 def _mulcheck(self, result): 534 if self._multiplier == None: 535 return result 536 return result * self._multiplier 537 538 def unproxy(self, base): 539 obj = base 540 done = False 541 542 if self._search_self: 543 result, done = self.find(obj) 544 545 if self._search_up: 546 while not done: 547 try: obj = obj._parent 548 except: break 549 550 result, done = self.find(obj) 551 552 if not done: 553 raise AttributeError, "Can't resolve proxy '%s' from '%s'" % \ 554 (self.path(), base.path()) 555 556 if isinstance(result, BaseProxy): 557 if result == self: 558 raise RuntimeError, "Cycle in unproxy" 559 result = result.unproxy(obj) 560 561 return self._mulcheck(result) 562 563 def getindex(obj, index): 564 if index == None: 565 return obj 566 try: 567 obj = obj[index] 568 except TypeError: 569 if index != 0: 570 raise 571 # if index is 0 and item is not subscriptable, just 572 # use item itself (so cpu[0] works on uniprocessors) 573 return obj 574 getindex = staticmethod(getindex) 575 576 def set_param_desc(self, pdesc): 577 self._pdesc = pdesc 578 579class AttrProxy(BaseProxy): 580 def __init__(self, search_self, search_up, attr): 581 super(AttrProxy, self).__init__(search_self, search_up) 582 self._attr = attr 583 self._modifiers = [] 584 585 def __getattr__(self, attr): 586 # python uses __bases__ internally for inheritance 587 if attr.startswith('_'): 588 return super(AttrProxy, self).__getattr__(self, attr) 589 if hasattr(self, '_pdesc'): 590 raise AttributeError, "Attribute reference on bound proxy" 591 self._modifiers.append(attr) 592 return self 593 594 # support indexing on proxies (e.g., Self.cpu[0]) 595 def __getitem__(self, key): 596 if not isinstance(key, int): 597 raise TypeError, "Proxy object requires integer index" 598 self._modifiers.append(key) 599 return self 600 601 def find(self, obj): 602 try: 603 val = getattr(obj, self._attr) 604 except: 605 return None, False 606 while isproxy(val): 607 val = val.unproxy(obj) 608 for m in self._modifiers: 609 if isinstance(m, str): 610 val = getattr(val, m) 611 elif isinstance(m, int): 612 val = val[m] 613 else: 614 assert("Item must be string or integer") 615 while isproxy(val): 616 val = val.unproxy(obj) 617 return val, True 618 619 def path(self): 620 p = self._attr 621 for m in self._modifiers: 622 if isinstance(m, str): 623 p += '.%s' % m 624 elif isinstance(m, int): 625 p += '[%d]' % m 626 else: 627 assert("Item must be string or integer") 628 return p 629 630class AnyProxy(BaseProxy): 631 def find(self, obj): 632 return obj.find_any(self._pdesc.ptype) 633 634 def path(self): 635 return 'any' 636 637def isproxy(obj): 638 if isinstance(obj, (BaseProxy, EthernetAddr)): 639 return True 640 elif isinstance(obj, (list, tuple)): 641 for v in obj: 642 if isproxy(v): 643 return True 644 return False 645 646class ProxyFactory(object): 647 def __init__(self, search_self, search_up): 648 self.search_self = search_self 649 self.search_up = search_up 650 651 def __getattr__(self, attr): 652 if attr == 'any': 653 return AnyProxy(self.search_self, self.search_up) 654 else: 655 return AttrProxy(self.search_self, self.search_up, attr) 656 657# global objects for handling proxies 658Parent = ProxyFactory(search_self = False, search_up = True) 659Self = ProxyFactory(search_self = True, search_up = False) 660 661##################################################################### 662# 663# Parameter description classes 664# 665# The _params dictionary in each class maps parameter names to 666# either a Param or a VectorParam object. These objects contain the 667# parameter description string, the parameter type, and the default 668# value (loaded from the PARAM section of the .odesc files). The 669# _convert() method on these objects is used to force whatever value 670# is assigned to the parameter to the appropriate type. 671# 672# Note that the default values are loaded into the class's attribute 673# space when the parameter dictionary is initialized (in 674# MetaConfigNode._setparams()); after that point they aren't used. 675# 676##################################################################### 677 678# Dummy base class to identify types that are legitimate for SimObject 679# parameters. 680class ParamValue(object): 681 682 # default for printing to .ini file is regular string conversion. 683 # will be overridden in some cases 684 def ini_str(self): 685 return str(self) 686 687 # allows us to blithely call unproxy() on things without checking 688 # if they're really proxies or not 689 def unproxy(self, base): 690 return self 691 692# Regular parameter description. 693class ParamDesc(object): 694 def __init__(self, ptype_str, ptype, *args, **kwargs): 695 self.ptype_str = ptype_str 696 # remember ptype only if it is provided 697 if ptype != None: 698 self.ptype = ptype 699 700 if args: 701 if len(args) == 1: 702 self.desc = args[0] 703 elif len(args) == 2: 704 self.default = args[0] 705 self.desc = args[1] 706 else: 707 raise TypeError, 'too many arguments' 708 709 if kwargs.has_key('desc'): 710 assert(not hasattr(self, 'desc')) 711 self.desc = kwargs['desc'] 712 del kwargs['desc'] 713 714 if kwargs.has_key('default'): 715 assert(not hasattr(self, 'default')) 716 self.default = kwargs['default'] 717 del kwargs['default'] 718 719 if kwargs: 720 raise TypeError, 'extra unknown kwargs %s' % kwargs 721 722 if not hasattr(self, 'desc'): 723 raise TypeError, 'desc attribute missing' 724 725 def __getattr__(self, attr): 726 if attr == 'ptype': 727 try: 728 ptype = eval(self.ptype_str, m5.__dict__) 729 if not isinstance(ptype, type): 730 panic("Param qualifier is not a type: %s" % self.ptype) 731 self.ptype = ptype 732 return ptype 733 except NameError: 734 pass 735 raise AttributeError, "'%s' object has no attribute '%s'" % \ 736 (type(self).__name__, attr) 737 738 def convert(self, value): 739 if isinstance(value, BaseProxy): 740 value.set_param_desc(self) 741 return value 742 if not hasattr(self, 'ptype') and isNullPointer(value): 743 # deferred evaluation of SimObject; continue to defer if 744 # we're just assigning a null pointer 745 return value 746 if isinstance(value, self.ptype): 747 return value 748 if isNullPointer(value) and issubclass(self.ptype, SimObject): 749 return value 750 return self.ptype(value) 751 752# Vector-valued parameter description. Just like ParamDesc, except 753# that the value is a vector (list) of the specified type instead of a 754# single value. 755 756class VectorParamValue(list): 757 def ini_str(self): 758 return ' '.join([str(v) for v in self]) 759 760 def unproxy(self, base): 761 return [v.unproxy(base) for v in self] 762 763class SimObjVector(VectorParamValue): 764 def print_ini(self): 765 for v in self: 766 v.print_ini() 767 768class VectorParamDesc(ParamDesc): 769 # Convert assigned value to appropriate type. If the RHS is not a 770 # list or tuple, it generates a single-element list. 771 def convert(self, value): 772 if isinstance(value, (list, tuple)): 773 # list: coerce each element into new list 774 tmp_list = [ ParamDesc.convert(self, v) for v in value ] 775 if isSimObjSequence(tmp_list): 776 return SimObjVector(tmp_list) 777 else: 778 return VectorParamValue(tmp_list) 779 else: 780 # singleton: leave it be (could coerce to a single-element 781 # list here, but for some historical reason we don't... 782 return ParamDesc.convert(self, value) 783 784 785class ParamFactory(object): 786 def __init__(self, param_desc_class, ptype_str = None): 787 self.param_desc_class = param_desc_class 788 self.ptype_str = ptype_str 789 790 def __getattr__(self, attr): 791 if self.ptype_str: 792 attr = self.ptype_str + '.' + attr 793 return ParamFactory(self.param_desc_class, attr) 794 795 # E.g., Param.Int(5, "number of widgets") 796 def __call__(self, *args, **kwargs): 797 caller_frame = inspect.stack()[1][0] 798 ptype = None 799 try: 800 ptype = eval(self.ptype_str, 801 caller_frame.f_globals, caller_frame.f_locals) 802 if not isinstance(ptype, type): 803 raise TypeError, \ 804 "Param qualifier is not a type: %s" % ptype 805 except NameError: 806 # if name isn't defined yet, assume it's a SimObject, and 807 # try to resolve it later 808 pass 809 return self.param_desc_class(self.ptype_str, ptype, *args, **kwargs) 810 811Param = ParamFactory(ParamDesc) 812VectorParam = ParamFactory(VectorParamDesc) 813 814##################################################################### 815# 816# Parameter Types 817# 818# Though native Python types could be used to specify parameter types 819# (the 'ptype' field of the Param and VectorParam classes), it's more 820# flexible to define our own set of types. This gives us more control 821# over how Python expressions are converted to values (via the 822# __init__() constructor) and how these values are printed out (via 823# the __str__() conversion method). Eventually we'll need these types 824# to correspond to distinct C++ types as well. 825# 826##################################################################### 827 828class Range(ParamValue): 829 type = int # default; can be overridden in subclasses 830 def __init__(self, *args, **kwargs): 831 832 def handle_kwargs(self, kwargs): 833 if 'end' in kwargs: 834 self.second = self.type(kwargs.pop('end')) 835 elif 'size' in kwargs: 836 self.second = self.first + self.type(kwargs.pop('size')) - 1 837 else: 838 raise TypeError, "Either end or size must be specified" 839 840 if len(args) == 0: 841 self.first = self.type(kwargs.pop('start')) 842 handle_kwargs(self, kwargs) 843 844 elif len(args) == 1: 845 if kwargs: 846 self.first = self.type(args[0]) 847 handle_kwargs(self, kwargs) 848 elif isinstance(args[0], Range): 849 self.first = self.type(args[0].first) 850 self.second = self.type(args[0].second) 851 else: 852 self.first = self.type(0) 853 self.second = self.type(args[0]) - 1 854 855 elif len(args) == 2: 856 self.first = self.type(args[0]) 857 self.second = self.type(args[1]) 858 else: 859 raise TypeError, "Too many arguments specified" 860 861 if kwargs: 862 raise TypeError, "too many keywords: %s" % kwargs.keys() 863 864 def __str__(self): 865 return '%s:%s' % (self.first, self.second) 866 867# Metaclass for bounds-checked integer parameters. See CheckedInt. 868class CheckedIntType(type): 869 def __init__(cls, name, bases, dict): 870 super(CheckedIntType, cls).__init__(name, bases, dict) 871 872 # CheckedInt is an abstract base class, so we actually don't 873 # want to do any processing on it... the rest of this code is 874 # just for classes that derive from CheckedInt. 875 if name == 'CheckedInt': 876 return 877 878 if not (hasattr(cls, 'min') and hasattr(cls, 'max')): 879 if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')): 880 panic("CheckedInt subclass %s must define either\n" \ 881 " 'min' and 'max' or 'size' and 'unsigned'\n" \ 882 % name); 883 if cls.unsigned: 884 cls.min = 0 885 cls.max = 2 ** cls.size - 1 886 else: 887 cls.min = -(2 ** (cls.size - 1)) 888 cls.max = (2 ** (cls.size - 1)) - 1 889 890# Abstract superclass for bounds-checked integer parameters. This 891# class is subclassed to generate parameter classes with specific 892# bounds. Initialization of the min and max bounds is done in the 893# metaclass CheckedIntType.__init__. 894class CheckedInt(long,ParamValue): 895 __metaclass__ = CheckedIntType 896 897 def __new__(cls, value): 898 if isinstance(value, str): 899 value = toInteger(value) 900 901 self = long.__new__(cls, value) 902 903 if not cls.min <= self <= cls.max: 904 raise TypeError, 'Integer param out of bounds %d < %d < %d' % \ 905 (cls.min, self, cls.max) 906 return self 907 908class Int(CheckedInt): size = 32; unsigned = False 909class Unsigned(CheckedInt): size = 32; unsigned = True 910 911class Int8(CheckedInt): size = 8; unsigned = False 912class UInt8(CheckedInt): size = 8; unsigned = True 913class Int16(CheckedInt): size = 16; unsigned = False 914class UInt16(CheckedInt): size = 16; unsigned = True 915class Int32(CheckedInt): size = 32; unsigned = False 916class UInt32(CheckedInt): size = 32; unsigned = True 917class Int64(CheckedInt): size = 64; unsigned = False 918class UInt64(CheckedInt): size = 64; unsigned = True 919 920class Counter(CheckedInt): size = 64; unsigned = True 921class Tick(CheckedInt): size = 64; unsigned = True 922class TcpPort(CheckedInt): size = 16; unsigned = True 923class UdpPort(CheckedInt): size = 16; unsigned = True 924 925class Percent(CheckedInt): min = 0; max = 100 926 927class Float(ParamValue, float): 928 pass 929 930class MemorySize(CheckedInt): 931 size = 64 932 unsigned = True 933 def __new__(cls, value): 934 return super(MemorySize, cls).__new__(cls, toMemorySize(value)) 935 936 937class Addr(CheckedInt): 938 size = 64 939 unsigned = True 940 def __new__(cls, value): 941 try: 942 value = long(toMemorySize(value)) 943 except TypeError: 944 value = long(value) 945 return super(Addr, cls).__new__(cls, value) 946 947class AddrRange(Range): 948 type = Addr 949 950# String-valued parameter. Just mixin the ParamValue class 951# with the built-in str class. 952class String(ParamValue,str): 953 pass 954 955# Boolean parameter type. Python doesn't let you subclass bool, since 956# it doesn't want to let you create multiple instances of True and 957# False. Thus this is a little more complicated than String. 958class Bool(ParamValue): 959 def __init__(self, value): 960 try: 961 self.value = toBool(value) 962 except TypeError: 963 self.value = bool(value) 964 965 def __str__(self): 966 return str(self.value) 967 968 def ini_str(self): 969 if self.value: 970 return 'true' 971 return 'false' 972 973def IncEthernetAddr(addr, val = 1): 974 bytes = map(lambda x: int(x, 16), addr.split(':')) 975 bytes[5] += val 976 for i in (5, 4, 3, 2, 1): 977 val,rem = divmod(bytes[i], 256) 978 bytes[i] = rem 979 if val == 0: 980 break 981 bytes[i - 1] += val 982 assert(bytes[0] <= 255) 983 return ':'.join(map(lambda x: '%02x' % x, bytes)) 984 985class NextEthernetAddr(object): 986 addr = "00:90:00:00:00:01" 987 988 def __init__(self, inc = 1): 989 self.value = NextEthernetAddr.addr 990 NextEthernetAddr.addr = IncEthernetAddr(NextEthernetAddr.addr, inc) 991 992class EthernetAddr(ParamValue): 993 def __init__(self, value): 994 if value == NextEthernetAddr: 995 self.value = value 996 return 997 998 if not isinstance(value, str): 999 raise TypeError, "expected an ethernet address and didn't get one" 1000 1001 bytes = value.split(':') 1002 if len(bytes) != 6: 1003 raise TypeError, 'invalid ethernet address %s' % value 1004 1005 for byte in bytes: 1006 if not 0 <= int(byte) <= 256: 1007 raise TypeError, 'invalid ethernet address %s' % value 1008 1009 self.value = value 1010 1011 def unproxy(self, base): 1012 if self.value == NextEthernetAddr: 1013 self.addr = self.value().value 1014 return self 1015 1016 def __str__(self): 1017 if self.value == NextEthernetAddr: 1018 return self.addr 1019 else: 1020 return self.value 1021 1022# Special class for NULL pointers. Note the special check in 1023# make_param_value() above that lets these be assigned where a 1024# SimObject is required. 1025# only one copy of a particular node 1026class NullSimObject(object): 1027 __metaclass__ = Singleton 1028 1029 def __call__(cls): 1030 return cls 1031 1032 def _instantiate(self, parent = None, path = ''): 1033 pass 1034 1035 def ini_str(self): 1036 return 'Null' 1037 1038 def unproxy(self, base): 1039 return self 1040 1041 def set_path(self, parent, name): 1042 pass 1043 def __str__(self): 1044 return 'Null' 1045 1046# The only instance you'll ever need... 1047Null = NULL = NullSimObject() 1048 1049# Enumerated types are a little more complex. The user specifies the 1050# type as Enum(foo) where foo is either a list or dictionary of 1051# alternatives (typically strings, but not necessarily so). (In the 1052# long run, the integer value of the parameter will be the list index 1053# or the corresponding dictionary value. For now, since we only check 1054# that the alternative is valid and then spit it into a .ini file, 1055# there's not much point in using the dictionary.) 1056 1057# What Enum() must do is generate a new type encapsulating the 1058# provided list/dictionary so that specific values of the parameter 1059# can be instances of that type. We define two hidden internal 1060# classes (_ListEnum and _DictEnum) to serve as base classes, then 1061# derive the new type from the appropriate base class on the fly. 1062 1063 1064# Metaclass for Enum types 1065class MetaEnum(type): 1066 def __init__(cls, name, bases, init_dict): 1067 if init_dict.has_key('map'): 1068 if not isinstance(cls.map, dict): 1069 raise TypeError, "Enum-derived class attribute 'map' " \ 1070 "must be of type dict" 1071 # build list of value strings from map 1072 cls.vals = cls.map.keys() 1073 cls.vals.sort() 1074 elif init_dict.has_key('vals'): 1075 if not isinstance(cls.vals, list): 1076 raise TypeError, "Enum-derived class attribute 'vals' " \ 1077 "must be of type list" 1078 # build string->value map from vals sequence 1079 cls.map = {} 1080 for idx,val in enumerate(cls.vals): 1081 cls.map[val] = idx 1082 else: 1083 raise TypeError, "Enum-derived class must define "\ 1084 "attribute 'map' or 'vals'" 1085 1086 super(MetaEnum, cls).__init__(name, bases, init_dict) 1087 1088 def cpp_declare(cls): 1089 s = 'enum %s {\n ' % cls.__name__ 1090 s += ',\n '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals]) 1091 s += '\n};\n' 1092 return s 1093 1094# Base class for enum types. 1095class Enum(ParamValue): 1096 __metaclass__ = MetaEnum 1097 vals = [] 1098 1099 def __init__(self, value): 1100 if value not in self.map: 1101 raise TypeError, "Enum param got bad value '%s' (not in %s)" \ 1102 % (value, self.vals) 1103 self.value = value 1104 1105 def __str__(self): 1106 return self.value 1107 1108ticks_per_sec = None 1109 1110# how big does a rounding error need to be before we warn about it? 1111frequency_tolerance = 0.001 # 0.1% 1112 1113# convert a floting-point # of ticks to integer, and warn if rounding 1114# discards too much precision 1115def tick_check(float_ticks): 1116 if float_ticks == 0: 1117 return 0 1118 int_ticks = int(round(float_ticks)) 1119 err = (float_ticks - int_ticks) / float_ticks 1120 if err > frequency_tolerance: 1121 print >> sys.stderr, "Warning: rounding error > tolerance" 1122 print >> sys.stderr, " %f rounded to %d" % (float_ticks, int_ticks) 1123 #raise ValueError 1124 return int_ticks 1125 1126# superclass for "numeric" parameter values, to emulate math 1127# operations in a type-safe way. e.g., a Latency times an int returns 1128# a new Latency object. 1129class NumericParamValue(ParamValue): 1130 def __str__(self): 1131 return str(self.value) 1132 1133 def __float__(self): 1134 return float(self.value) 1135 1136 def __mul__(self, other): 1137 newobj = self.__class__(self) 1138 newobj.value *= other 1139 return newobj 1140 1141 __rmul__ = __mul__ 1142 1143 def __div__(self, other): 1144 newobj = self.__class__(self) 1145 newobj.value /= other 1146 return newobj 1147 1148 1149def getLatency(value): 1150 if isinstance(value, Latency) or isinstance(value, Clock): 1151 return value.value 1152 elif isinstance(value, Frequency) or isinstance(value, RootClock): 1153 return 1 / value.value 1154 elif isinstance(value, str): 1155 try: 1156 return toLatency(value) 1157 except ValueError: 1158 try: 1159 return 1 / toFrequency(value) 1160 except ValueError: 1161 pass # fall through 1162 raise ValueError, "Invalid Frequency/Latency value '%s'" % value 1163 1164 1165class Latency(NumericParamValue): 1166 def __init__(self, value): 1167 self.value = getLatency(value) 1168 1169 def __getattr__(self, attr): 1170 if attr in ('latency', 'period'): 1171 return self 1172 if attr == 'frequency': 1173 return Frequency(self) 1174 raise AttributeError, "Latency object has no attribute '%s'" % attr 1175 1176 # convert latency to ticks 1177 def ini_str(self): 1178 return str(tick_check(self.value * ticks_per_sec)) 1179 1180class Frequency(NumericParamValue): 1181 def __init__(self, value): 1182 self.value = 1 / getLatency(value) 1183 1184 def __getattr__(self, attr): 1185 if attr == 'frequency': 1186 return self 1187 if attr in ('latency', 'period'): 1188 return Latency(self) 1189 raise AttributeError, "Frequency object has no attribute '%s'" % attr 1190 1191 # convert frequency to ticks per period 1192 def ini_str(self): 1193 return self.period.ini_str() 1194 1195# Just like Frequency, except ini_str() is absolute # of ticks per sec (Hz). 1196# We can't inherit from Frequency because we don't want it to be directly 1197# assignable to a regular Frequency parameter. 1198class RootClock(ParamValue): 1199 def __init__(self, value): 1200 self.value = 1 / getLatency(value) 1201 1202 def __getattr__(self, attr): 1203 if attr == 'frequency': 1204 return Frequency(self) 1205 if attr in ('latency', 'period'): 1206 return Latency(self) 1207 raise AttributeError, "Frequency object has no attribute '%s'" % attr 1208 1209 def ini_str(self): 1210 return str(tick_check(self.value)) 1211 1212# A generic frequency and/or Latency value. Value is stored as a latency, 1213# but to avoid ambiguity this object does not support numeric ops (* or /). 1214# An explicit conversion to a Latency or Frequency must be made first. 1215class Clock(ParamValue): 1216 def __init__(self, value): 1217 self.value = getLatency(value) 1218 1219 def __getattr__(self, attr): 1220 if attr == 'frequency': 1221 return Frequency(self) 1222 if attr in ('latency', 'period'): 1223 return Latency(self) 1224 raise AttributeError, "Frequency object has no attribute '%s'" % attr 1225 1226 def ini_str(self): 1227 return self.period.ini_str() 1228 1229class NetworkBandwidth(float,ParamValue): 1230 def __new__(cls, value): 1231 val = toNetworkBandwidth(value) / 8.0 1232 return super(cls, NetworkBandwidth).__new__(cls, val) 1233 1234 def __str__(self): 1235 return str(self.val) 1236 1237 def ini_str(self): 1238 return '%f' % (ticks_per_sec / float(self)) 1239 1240class MemoryBandwidth(float,ParamValue): 1241 def __new__(self, value): 1242 val = toMemoryBandwidth(value) 1243 return super(cls, MemoryBandwidth).__new__(cls, val) 1244 1245 def __str__(self): 1246 return str(self.val) 1247 1248 def ini_str(self): 1249 return '%f' % (ticks_per_sec / float(self)) 1250 1251# 1252# "Constants"... handy aliases for various values. 1253# 1254 1255# Some memory range specifications use this as a default upper bound. 1256MaxAddr = Addr.max 1257MaxTick = Tick.max 1258AllMemory = AddrRange(0, MaxAddr) 1259 1260##################################################################### 1261 1262# The final hook to generate .ini files. Called from configuration 1263# script once config is built. 1264def instantiate(root): 1265 global ticks_per_sec 1266 ticks_per_sec = float(root.clock.frequency) 1267 root.print_ini() 1268 noDot = True # temporary until we fix dot 1269 if not noDot: 1270 dot = pydot.Dot() 1271 instance.outputDot(dot) 1272 dot.orientation = "portrait" 1273 dot.size = "8.5,11" 1274 dot.ranksep="equally" 1275 dot.rank="samerank" 1276 dot.write("config.dot") 1277 dot.write_ps("config.ps") 1278 1279# __all__ defines the list of symbols that get exported when 1280# 'from config import *' is invoked. Try to keep this reasonably 1281# short to avoid polluting other namespaces. 1282__all__ = ['SimObject', 'ParamContext', 'Param', 'VectorParam', 1283 'Parent', 'Self', 1284 'Enum', 'Bool', 'String', 'Float', 1285 'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16', 1286 'Int32', 'UInt32', 'Int64', 'UInt64', 1287 'Counter', 'Addr', 'Tick', 'Percent', 1288 'TcpPort', 'UdpPort', 'EthernetAddr', 1289 'MemorySize', 'Latency', 'Frequency', 'RootClock', 'Clock', 1290 'NetworkBandwidth', 'MemoryBandwidth', 1291 'Range', 'AddrRange', 'MaxAddr', 'MaxTick', 'AllMemory', 1292 'Null', 'NULL', 1293 'NextEthernetAddr', 'instantiate'] 1294 1295