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