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