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