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