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