SimObject.py revision 1587
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 206 result, done = obj.find(ptype, self._path) 207 obj = obj.parent 208 209 if isinstance(result, Proxy): 210 result = result.unproxy(obj, ptype) 211 212 return self._mulcheck(result) 213 214 def getindex(obj, index): 215 if index == None: 216 return obj 217 try: 218 obj = obj[index] 219 except TypeError: 220 if index != 0: 221 raise 222 # if index is 0 and item is not subscriptable, just 223 # use item itself (so cpu[0] works on uniprocessors) 224 return obj 225 getindex = staticmethod(getindex) 226 227class ProxyFactory(object): 228 def __getattr__(self, attr): 229 return Proxy(attr) 230 231# global object for handling parent.foo proxies 232parent = ProxyFactory() 233 234def isSubClass(value, cls): 235 try: 236 return issubclass(value, cls) 237 except: 238 return False 239 240def isConfigNode(value): 241 try: 242 return issubclass(value, ConfigNode) 243 except: 244 return False 245 246def isSimObject(value): 247 try: 248 return issubclass(value, SimObject) 249 except: 250 return False 251 252def isSimObjSequence(value): 253 if not isinstance(value, (list, tuple)): 254 return False 255 256 for val in value: 257 if not isNullPointer(val) and not isConfigNode(val): 258 return False 259 260 return True 261 262def isParamContext(value): 263 try: 264 return issubclass(value, ParamContext) 265 except: 266 return False 267 268 269class_decorator = 'M5M5_SIMOBJECT_' 270expr_decorator = 'M5M5_EXPRESSION_' 271dot_decorator = '_M5M5_DOT_' 272 273# 'Global' map of legitimate types for SimObject parameters. 274param_types = {} 275 276# Dummy base class to identify types that are legitimate for SimObject 277# parameters. 278class ParamType(object): 279 pass 280 281# Add types defined in given context (dict or module) that are derived 282# from ParamType to param_types map. 283def add_param_types(ctx): 284 if isinstance(ctx, types.DictType): 285 source_dict = ctx 286 elif isinstance(ctx, types.ModuleType): 287 source_dict = ctx.__dict__ 288 else: 289 raise TypeError, \ 290 "m5.config.add_param_types requires dict or module as arg" 291 for key,val in source_dict.iteritems(): 292 if isinstance(val, type) and issubclass(val, ParamType): 293 param_types[key] = val 294 295# The metaclass for ConfigNode (and thus for everything that derives 296# from ConfigNode, including SimObject). This class controls how new 297# classes that derive from ConfigNode are instantiated, and provides 298# inherited class behavior (just like a class controls how instances 299# of that class are instantiated, and provides inherited instance 300# behavior). 301class MetaConfigNode(type): 302 # Attributes that can be set only at initialization time 303 init_keywords = {} 304 # Attributes that can be set any time 305 keywords = { 'check' : types.FunctionType, 306 'children' : types.ListType } 307 308 # __new__ is called before __init__, and is where the statements 309 # in the body of the class definition get loaded into the class's 310 # __dict__. We intercept this to filter out parameter assignments 311 # and only allow "private" attributes to be passed to the base 312 # __new__ (starting with underscore). 313 def __new__(mcls, name, bases, dict): 314 # Copy "private" attributes (including special methods such as __new__) 315 # to the official dict. Everything else goes in _init_dict to be 316 # filtered in __init__. 317 cls_dict = {} 318 for key,val in dict.items(): 319 if key.startswith('_'): 320 cls_dict[key] = val 321 del dict[key] 322 cls_dict['_init_dict'] = dict 323 return super(MetaConfigNode, mcls).__new__(mcls, name, bases, cls_dict) 324 325 # initialization 326 def __init__(cls, name, bases, dict): 327 super(MetaConfigNode, cls).__init__(name, bases, dict) 328 329 # initialize required attributes 330 cls._params = multidict() 331 cls._values = multidict() 332 cls._param_types = {} 333 cls._bases = [c for c in cls.__mro__ if isConfigNode(c)] 334 cls._anon_subclass_counter = 0 335 336 # We don't support multiple inheritence. If you want to, you 337 # must fix multidict to deal with it properly. 338 sob = [ base for base in bases \ 339 if issubclass(base, ParamType) and base != ParamType ] 340 341 if len(sob) == 1: 342 # If your parent has a value in it that's a config node, clone 343 # it. Do this now so if we update any of the values' 344 # attributes we are updating the clone and not the original. 345 for key,val in sob[0]._values.iteritems(): 346 347 # don't clone if (1) we're about to overwrite it with 348 # a local setting or (2) we've already cloned a copy 349 # from an earlier (more derived) base 350 if cls._init_dict.has_key(key) or cls._values.has_key(key): 351 continue 352 353 if isConfigNode(val): 354 cls._values[key] = val() 355 elif isSimObjSequence(val) and len(val): 356 cls._values[key] = [ v() for v in val ] 357 358 cls._params.parent = sob[0]._params 359 cls._values.parent = sob[0]._values 360 361 elif len(sob) > 1: 362 panic("""\ 363The config hierarchy only supports single inheritence of SimObject 364classes. You're trying to derive from: 365%s""" % str(sob)) 366 367 # process param types from _init_dict, as these may be needed 368 # by param descriptions also in _init_dict 369 for key,val in cls._init_dict.items(): 370 if isinstance(val, type) and issubclass(val, ParamType): 371 cls._param_types[key] = val 372 if not issubclass(val, ConfigNode): 373 del cls._init_dict[key] 374 375 # now process remaining _init_dict items 376 for key,val in cls._init_dict.items(): 377 # param descriptions 378 if isinstance(val, _Param): 379 cls._new_param(key, val) 380 381 # init-time-only keywords 382 elif cls.init_keywords.has_key(key): 383 cls._set_keyword(key, val, cls.init_keywords[key]) 384 385 # See description of decorators in the importer.py file. 386 # We just strip off the expr_decorator now since we don't 387 # need from this point on. 388 elif key.startswith(expr_decorator): 389 key = key[len(expr_decorator):] 390 # because it had dots into a list so that we can find the 391 # proper variable to modify. 392 key = key.split(dot_decorator) 393 c = cls 394 for item in key[:-1]: 395 c = getattr(c, item) 396 setattr(c, key[-1], val) 397 398 # default: use normal path (ends up in __setattr__) 399 else: 400 setattr(cls, key, val) 401 402 def _set_keyword(cls, keyword, val, kwtype): 403 if not isinstance(val, kwtype): 404 raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \ 405 (keyword, type(val), kwtype) 406 if isinstance(val, types.FunctionType): 407 val = classmethod(val) 408 type.__setattr__(cls, keyword, val) 409 410 def _new_param(cls, name, value): 411 cls._params[name] = value 412 if hasattr(value, 'default'): 413 cls._values[name] = value.default 414 # try to resolve local param types in local param_types scope 415 value.maybe_resolve_type(cls._param_types) 416 417 # Set attribute (called on foo.attr = value when foo is an 418 # instance of class cls). 419 def __setattr__(cls, attr, value): 420 # normal processing for private attributes 421 if attr.startswith('_'): 422 type.__setattr__(cls, attr, value) 423 return 424 425 if cls.keywords.has_key(attr): 426 cls._set_keyword(attr, value, cls.keywords[attr]) 427 return 428 429 # must be SimObject param 430 param = cls._params.get(attr, None) 431 if param: 432 # It's ok: set attribute by delegating to 'object' class. 433 # Note the use of param.make_value() to verify/canonicalize 434 # the assigned value 435 try: 436 param.valid(value) 437 except Exception, e: 438 panic("Exception: %s\nError setting param %s.%s to %s\n" % \ 439 (e, cls.__name__, attr, value)) 440 cls._values[attr] = value 441 elif isConfigNode(value) or isSimObjSequence(value): 442 cls._values[attr] = value 443 else: 444 raise AttributeError, \ 445 "Class %s has no parameter %s" % (cls.__name__, attr) 446 447 def __getattr__(cls, attr): 448 if cls._params.has_key(attr) or cls._values.has_key(attr): 449 return Value(cls, attr) 450 451 if attr == '_cpp_param_decl' and hasattr(cls, 'type'): 452 return cls.type + '*' 453 454 raise AttributeError, \ 455 "object '%s' has no attribute '%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 isinstance(child, (list, tuple)): 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._values.iteritems(): 487 if isConfigNode(value): 488 cls.add_child(instance, key, value) 489 if isinstance(value, (list, tuple)): 490 vals = [ v for v in value if isConfigNode(v) ] 491 if len(vals): 492 cls.add_child(instance, key, vals) 493 494 for pname,param in cls._params.iteritems(): 495 value = cls._values.get(pname, None) 496 if value is None: 497 panic('Error getting %s from %s' % (pname, name)) 498 499 try: 500 if isConfigNode(value): 501 value = instance.child_objects[value] 502 elif isinstance(value, (list, tuple)): 503 v = [] 504 for val in value: 505 if isConfigNode(val): 506 v.append(instance.child_objects[val]) 507 else: 508 v.append(val) 509 value = v 510 511 p = NodeParam(pname, param, value) 512 instance.params.append(p) 513 instance.param_names[pname] = p 514 except Exception, e: 515 raise e.__class__, 'Exception while evaluating %s.%s\n%s' % \ 516 (instance.path, pname, e) 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 Exception, e: 698 raise e.__class__, 'Error while fixing up %s:%s\n%s' % \ 699 (self.path, param.name, e) 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 Exception, e: 732 raise e.__class__, 'exception in %s:%s\n%s' % \ 733 (self.path, param.name, e) 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 Exception, e: 767 raise e.__class__, 'exception in %s:%s\n%s' % \ 768 (self.name, param.name, e) 769 raise 770 if isConfigNode(param.ptype) and string != "Null": 771 simobjs.append(string) 772 else: 773 label += '%s = %s\\n' % (param.name, string) 774 775 for so in simobjs: 776 label += "|<%s> %s" % (so, so) 777 dot.add_edge(pydot.Edge("%s:%s" % (self.path, so), so, 778 tailport="w")) 779 label += '}' 780 dot.add_node(pydot.Node(self.path,shape="Mrecord",label=label)) 781 782 # recursively dump out children 783 for c in self.children: 784 c.outputDot(dot) 785 786 def _string(cls, value): 787 if not isinstance(value, Node): 788 raise AttributeError, 'expecting %s got %s' % (Node, value) 789 return value.path 790 _string = classmethod(_string) 791 792##################################################################### 793# 794# Parameter description classes 795# 796# The _params dictionary in each class maps parameter names to 797# either a Param or a VectorParam object. These objects contain the 798# parameter description string, the parameter type, and the default 799# value (loaded from the PARAM section of the .odesc files). The 800# _convert() method on these objects is used to force whatever value 801# is assigned to the parameter to the appropriate type. 802# 803# Note that the default values are loaded into the class's attribute 804# space when the parameter dictionary is initialized (in 805# MetaConfigNode._setparams()); after that point they aren't used. 806# 807##################################################################### 808 809def isNullPointer(value): 810 return isinstance(value, NullSimObject) 811 812class Value(object): 813 def __init__(self, obj, attr): 814 super(Value, self).__setattr__('attr', attr) 815 super(Value, self).__setattr__('obj', obj) 816 817 def _getattr(self): 818 return self.obj._values.get(self.attr) 819 820 def __setattr__(self, attr, value): 821 setattr(self._getattr(), attr, value) 822 823 def __getattr__(self, attr): 824 return getattr(self._getattr(), attr) 825 826 def __getitem__(self, index): 827 return self._getattr().__getitem__(index) 828 829 def __call__(self, *args, **kwargs): 830 return self._getattr().__call__(*args, **kwargs) 831 832 def __nonzero__(self): 833 return bool(self._getattr()) 834 835 def __str__(self): 836 return str(self._getattr()) 837 838 def __len__(self): 839 return len(self._getattr()) 840 841# Regular parameter. 842class _Param(object): 843 def __init__(self, ptype, *args, **kwargs): 844 if isinstance(ptype, types.StringType): 845 self.ptype_string = ptype 846 elif isinstance(ptype, type): 847 self.ptype = ptype 848 else: 849 raise TypeError, "Param type is not a type (%s)" % ptype 850 851 if args: 852 if len(args) == 1: 853 self.desc = args[0] 854 elif len(args) == 2: 855 self.default = args[0] 856 self.desc = args[1] 857 else: 858 raise TypeError, 'too many arguments' 859 860 if kwargs.has_key('desc'): 861 assert(not hasattr(self, 'desc')) 862 self.desc = kwargs['desc'] 863 del kwargs['desc'] 864 865 if kwargs.has_key('default'): 866 assert(not hasattr(self, 'default')) 867 self.default = kwargs['default'] 868 del kwargs['default'] 869 870 if kwargs: 871 raise TypeError, 'extra unknown kwargs %s' % kwargs 872 873 if not hasattr(self, 'desc'): 874 raise TypeError, 'desc attribute missing' 875 876 def maybe_resolve_type(self, context): 877 # check if already resolved... don't use hasattr(), 878 # as that calls __getattr__() 879 if self.__dict__.has_key('ptype'): 880 return 881 try: 882 self.ptype = context[self.ptype_string] 883 except KeyError: 884 # no harm in trying... we'll try again later using global scope 885 pass 886 887 def __getattr__(self, attr): 888 if attr == 'ptype': 889 try: 890 self.ptype = param_types[self.ptype_string] 891 return self.ptype 892 except: 893 panic("undefined Param type %s" % self.ptype_string) 894 else: 895 raise AttributeError, "'%s' object has no attribute '%s'" % \ 896 (type(self).__name__, attr) 897 898 def valid(self, value): 899 if not isinstance(value, Proxy): 900 self.ptype._convert(value) 901 902 def convert(self, value): 903 return self.ptype._convert(value) 904 905 def string(self, value): 906 return self.ptype._string(value) 907 908 def set(self, name, instance, value): 909 instance.__dict__[name] = value 910 911 def cpp_decl(self, name): 912 return '%s %s;' % (self.ptype._cpp_param_decl, name) 913 914class _ParamProxy(object): 915 def __init__(self, type): 916 self.ptype = type 917 918 # E.g., Param.Int(5, "number of widgets") 919 def __call__(self, *args, **kwargs): 920 return _Param(self.ptype, *args, **kwargs) 921 922 # Strange magic to theoretically allow dotted names as Param classes, 923 # e.g., Param.Foo.Bar(...) to have a param of type Foo.Bar 924 def __getattr__(self, attr): 925 if attr == '__bases__': 926 raise AttributeError, '' 927 cls = type(self) 928 return cls(attr) 929 930 def __setattr__(self, attr, value): 931 if attr != 'ptype': 932 raise AttributeError, \ 933 'Attribute %s not available in %s' % (attr, self.__class__) 934 super(_ParamProxy, self).__setattr__(attr, value) 935 936 937Param = _ParamProxy(None) 938 939# Vector-valued parameter description. Just like Param, except that 940# the value is a vector (list) of the specified type instead of a 941# single value. 942class _VectorParam(_Param): 943 def __init__(self, type, *args, **kwargs): 944 _Param.__init__(self, type, *args, **kwargs) 945 946 def valid(self, value): 947 if value == None: 948 return True 949 950 if isinstance(value, (list, tuple)): 951 for val in value: 952 if not isinstance(val, Proxy): 953 self.ptype._convert(val) 954 elif not isinstance(value, Proxy): 955 self.ptype._convert(value) 956 957 # Convert assigned value to appropriate type. If the RHS is not a 958 # list or tuple, it generates a single-element list. 959 def convert(self, value): 960 if value == None: 961 return [] 962 963 if isinstance(value, (list, tuple)): 964 # list: coerce each element into new list 965 return [ self.ptype._convert(v) for v in value ] 966 else: 967 # singleton: coerce & wrap in a list 968 return self.ptype._convert(value) 969 970 def string(self, value): 971 if isinstance(value, (list, tuple)): 972 return ' '.join([ self.ptype._string(v) for v in value]) 973 else: 974 return self.ptype._string(value) 975 976 def cpp_decl(self, name): 977 return 'std::vector<%s> %s;' % (self.ptype._cpp_param_decl, name) 978 979class _VectorParamProxy(_ParamProxy): 980 # E.g., VectorParam.Int(5, "number of widgets") 981 def __call__(self, *args, **kwargs): 982 return _VectorParam(self.ptype, *args, **kwargs) 983 984VectorParam = _VectorParamProxy(None) 985 986##################################################################### 987# 988# Parameter Types 989# 990# Though native Python types could be used to specify parameter types 991# (the 'ptype' field of the Param and VectorParam classes), it's more 992# flexible to define our own set of types. This gives us more control 993# over how Python expressions are converted to values (via the 994# __init__() constructor) and how these values are printed out (via 995# the __str__() conversion method). Eventually we'll need these types 996# to correspond to distinct C++ types as well. 997# 998##################################################################### 999 1000 1001# Metaclass for bounds-checked integer parameters. See CheckedInt. 1002class CheckedIntType(type): 1003 def __init__(cls, name, bases, dict): 1004 super(CheckedIntType, cls).__init__(name, bases, dict) 1005 1006 # CheckedInt is an abstract base class, so we actually don't 1007 # want to do any processing on it... the rest of this code is 1008 # just for classes that derive from CheckedInt. 1009 if name == 'CheckedInt': 1010 return 1011 1012 if not (hasattr(cls, 'min') and hasattr(cls, 'max')): 1013 if not (hasattr(cls, 'size') and hasattr(cls, 'unsigned')): 1014 panic("CheckedInt subclass %s must define either\n" \ 1015 " 'min' and 'max' or 'size' and 'unsigned'\n" \ 1016 % name); 1017 if cls.unsigned: 1018 cls.min = 0 1019 cls.max = 2 ** cls.size - 1 1020 else: 1021 cls.min = -(2 ** (cls.size - 1)) 1022 cls.max = (2 ** (cls.size - 1)) - 1 1023 1024 cls._cpp_param_decl = cls.cppname 1025 1026 def _convert(cls, value): 1027 if isinstance(value, bool): 1028 return int(value) 1029 1030 if not isinstance(value, (int, long, float, str)): 1031 raise TypeError, 'Integer param of invalid type %s' % type(value) 1032 1033 if isinstance(value, (str, float)): 1034 value = long(float(value)) 1035 1036 if not cls.min <= value <= cls.max: 1037 raise TypeError, 'Integer param out of bounds %d < %d < %d' % \ 1038 (cls.min, value, cls.max) 1039 1040 return value 1041 1042 def _string(cls, value): 1043 return str(value) 1044 1045# Abstract superclass for bounds-checked integer parameters. This 1046# class is subclassed to generate parameter classes with specific 1047# bounds. Initialization of the min and max bounds is done in the 1048# metaclass CheckedIntType.__init__. 1049class CheckedInt(ParamType): 1050 __metaclass__ = CheckedIntType 1051 1052class Int(CheckedInt): cppname = 'int'; size = 32; unsigned = False 1053class Unsigned(CheckedInt): cppname = 'unsigned'; size = 32; unsigned = True 1054 1055class Int8(CheckedInt): cppname = 'int8_t'; size = 8; unsigned = False 1056class UInt8(CheckedInt): cppname = 'uint8_t'; size = 8; unsigned = True 1057class Int16(CheckedInt): cppname = 'int16_t'; size = 16; unsigned = False 1058class UInt16(CheckedInt): cppname = 'uint16_t'; size = 16; unsigned = True 1059class Int32(CheckedInt): cppname = 'int32_t'; size = 32; unsigned = False 1060class UInt32(CheckedInt): cppname = 'uint32_t'; size = 32; unsigned = True 1061class Int64(CheckedInt): cppname = 'int64_t'; size = 64; unsigned = False 1062class UInt64(CheckedInt): cppname = 'uint64_t'; size = 64; unsigned = True 1063 1064class Counter(CheckedInt): cppname = 'Counter'; size = 64; unsigned = True 1065class Addr(CheckedInt): cppname = 'Addr'; size = 64; unsigned = True 1066class Tick(CheckedInt): cppname = 'Tick'; size = 64; unsigned = True 1067 1068class Percent(CheckedInt): cppname = 'int'; min = 0; max = 100 1069 1070class Pair(object): 1071 def __init__(self, first, second): 1072 self.first = first 1073 self.second = second 1074 1075class MetaRange(type): 1076 def __init__(cls, name, bases, dict): 1077 super(MetaRange, cls).__init__(name, bases, dict) 1078 if name == 'Range': 1079 return 1080 cls._cpp_param_decl = 'Range<%s>' % cls.type._cpp_param_decl 1081 1082 def _convert(cls, value): 1083 if not isinstance(value, Pair): 1084 raise TypeError, 'value %s is not a Pair' % value 1085 return Pair(cls.type._convert(value.first), 1086 cls.type._convert(value.second)) 1087 1088 def _string(cls, value): 1089 return '%s:%s' % (cls.type._string(value.first), 1090 cls.type._string(value.second)) 1091 1092class Range(ParamType): 1093 __metaclass__ = MetaRange 1094 1095def RangeSize(start, size): 1096 return Pair(start, start + size - 1) 1097 1098class AddrRange(Range): type = Addr 1099 1100# Boolean parameter type. 1101class Bool(ParamType): 1102 _cpp_param_decl = 'bool' 1103 def _convert(value): 1104 t = type(value) 1105 if t == bool: 1106 return value 1107 1108 if t == int or t == long: 1109 return bool(value) 1110 1111 if t == str: 1112 v = value.lower() 1113 if v == "true" or v == "t" or v == "yes" or v == "y": 1114 return True 1115 elif v == "false" or v == "f" or v == "no" or v == "n": 1116 return False 1117 1118 raise TypeError, 'Bool parameter (%s) of invalid type %s' % (v, t) 1119 _convert = staticmethod(_convert) 1120 1121 def _string(value): 1122 if value: 1123 return "true" 1124 else: 1125 return "false" 1126 _string = staticmethod(_string) 1127 1128# String-valued parameter. 1129class String(ParamType): 1130 _cpp_param_decl = 'string' 1131 1132 # Constructor. Value must be Python string. 1133 def _convert(cls,value): 1134 if value is None: 1135 return '' 1136 if isinstance(value, str): 1137 return value 1138 1139 raise TypeError, \ 1140 "String param got value %s %s" % (repr(value), type(value)) 1141 _convert = classmethod(_convert) 1142 1143 # Generate printable string version. Not too tricky. 1144 def _string(cls, value): 1145 return value 1146 _string = classmethod(_string) 1147 1148 1149def IncEthernetAddr(addr, val = 1): 1150 bytes = map(lambda x: int(x, 16), addr.split(':')) 1151 bytes[5] += val 1152 for i in (5, 4, 3, 2, 1): 1153 val,rem = divmod(bytes[i], 256) 1154 bytes[i] = rem 1155 if val == 0: 1156 break 1157 bytes[i - 1] += val 1158 assert(bytes[0] <= 255) 1159 return ':'.join(map(lambda x: '%02x' % x, bytes)) 1160 1161class NextEthernetAddr(object): 1162 __metaclass__ = Singleton 1163 addr = "00:90:00:00:00:01" 1164 1165 def __init__(self, inc = 1): 1166 self.value = self.addr 1167 self.addr = IncEthernetAddr(self.addr, inc) 1168 1169class EthernetAddr(ParamType): 1170 _cpp_param_decl = 'EthAddr' 1171 1172 def _convert(cls, value): 1173 if value == NextEthernetAddr: 1174 return value 1175 1176 if not isinstance(value, str): 1177 raise TypeError, "expected an ethernet address and didn't get one" 1178 1179 bytes = value.split(':') 1180 if len(bytes) != 6: 1181 raise TypeError, 'invalid ethernet address %s' % value 1182 1183 for byte in bytes: 1184 if not 0 <= int(byte) <= 256: 1185 raise TypeError, 'invalid ethernet address %s' % value 1186 1187 return value 1188 _convert = classmethod(_convert) 1189 1190 def _string(cls, value): 1191 if value == NextEthernetAddr: 1192 value = value().value 1193 return value 1194 _string = classmethod(_string) 1195 1196# Special class for NULL pointers. Note the special check in 1197# make_param_value() above that lets these be assigned where a 1198# SimObject is required. 1199# only one copy of a particular node 1200class NullSimObject(object): 1201 __metaclass__ = Singleton 1202 1203 def __call__(cls): 1204 return cls 1205 1206 def _instantiate(self, parent = None, path = ''): 1207 pass 1208 1209 def _convert(cls, value): 1210 if value == Nxone: 1211 return 1212 1213 if isinstance(value, cls): 1214 return value 1215 1216 raise TypeError, 'object %s %s of the wrong type, should be %s' % \ 1217 (repr(value), type(value), cls) 1218 _convert = classmethod(_convert) 1219 1220 def _string(): 1221 return 'NULL' 1222 _string = staticmethod(_string) 1223 1224# The only instance you'll ever need... 1225Null = NULL = NullSimObject() 1226 1227# Enumerated types are a little more complex. The user specifies the 1228# type as Enum(foo) where foo is either a list or dictionary of 1229# alternatives (typically strings, but not necessarily so). (In the 1230# long run, the integer value of the parameter will be the list index 1231# or the corresponding dictionary value. For now, since we only check 1232# that the alternative is valid and then spit it into a .ini file, 1233# there's not much point in using the dictionary.) 1234 1235# What Enum() must do is generate a new type encapsulating the 1236# provided list/dictionary so that specific values of the parameter 1237# can be instances of that type. We define two hidden internal 1238# classes (_ListEnum and _DictEnum) to serve as base classes, then 1239# derive the new type from the appropriate base class on the fly. 1240 1241 1242# Metaclass for Enum types 1243class MetaEnum(type): 1244 1245 def __init__(cls, name, bases, init_dict): 1246 if init_dict.has_key('map'): 1247 if not isinstance(cls.map, dict): 1248 raise TypeError, "Enum-derived class attribute 'map' " \ 1249 "must be of type dict" 1250 # build list of value strings from map 1251 cls.vals = cls.map.keys() 1252 cls.vals.sort() 1253 elif init_dict.has_key('vals'): 1254 if not isinstance(cls.vals, list): 1255 raise TypeError, "Enum-derived class attribute 'vals' " \ 1256 "must be of type list" 1257 # build string->value map from vals sequence 1258 cls.map = {} 1259 for idx,val in enumerate(cls.vals): 1260 cls.map[val] = idx 1261 else: 1262 raise TypeError, "Enum-derived class must define "\ 1263 "attribute 'map' or 'vals'" 1264 1265 cls._cpp_param_decl = name 1266 1267 super(MetaEnum, cls).__init__(name, bases, init_dict) 1268 1269 def cpp_declare(cls): 1270 s = 'enum %s {\n ' % cls.__name__ 1271 s += ',\n '.join(['%s = %d' % (v,cls.map[v]) for v in cls.vals]) 1272 s += '\n};\n' 1273 return s 1274 1275# Base class for enum types. 1276class Enum(ParamType): 1277 __metaclass__ = MetaEnum 1278 vals = [] 1279 1280 def _convert(self, value): 1281 if value not in self.map: 1282 raise TypeError, "Enum param got bad value '%s' (not in %s)" \ 1283 % (value, self.vals) 1284 return value 1285 _convert = classmethod(_convert) 1286 1287 # Generate printable string version of value. 1288 def _string(self, value): 1289 return str(value) 1290 _string = classmethod(_string) 1291# 1292# "Constants"... handy aliases for various values. 1293# 1294 1295# Some memory range specifications use this as a default upper bound. 1296MAX_ADDR = Addr.max 1297MaxTick = Tick.max 1298 1299# For power-of-two sizing, e.g. 64*K gives an integer value 65536. 1300K = 1024 1301M = K*K 1302G = K*M 1303 1304##################################################################### 1305 1306# The final hook to generate .ini files. Called from configuration 1307# script once config is built. 1308def instantiate(root): 1309 instance = root.instantiate('root') 1310 instance.fixup() 1311 instance.display() 1312 if not noDot: 1313 dot = pydot.Dot() 1314 instance.outputDot(dot) 1315 dot.orientation = "portrait" 1316 dot.size = "8.5,11" 1317 dot.ranksep="equally" 1318 dot.rank="samerank" 1319 dot.write("config.dot") 1320 dot.write_ps("config.ps") 1321 1322# SimObject is a minimal extension of ConfigNode, implementing a 1323# hierarchy node that corresponds to an M5 SimObject. It prints out a 1324# "type=" line to indicate its SimObject class, prints out the 1325# assigned parameters corresponding to its class, and allows 1326# parameters to be set by keyword in the constructor. Note that most 1327# of the heavy lifting for the SimObject param handling is done in the 1328# MetaConfigNode metaclass. 1329class SimObject(ConfigNode, ParamType): 1330 __metaclass__ = MetaSimObject 1331 type = 'SimObject' 1332 1333 1334# __all__ defines the list of symbols that get exported when 1335# 'from config import *' is invoked. Try to keep this reasonably 1336# short to avoid polluting other namespaces. 1337__all__ = ['ConfigNode', 'SimObject', 'ParamContext', 'Param', 'VectorParam', 1338 'parent', 'Enum', 1339 'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16', 1340 'Int32', 'UInt32', 'Int64', 'UInt64', 1341 'Counter', 'Addr', 'Tick', 'Percent', 1342 'Pair', 'RangeSize', 'AddrRange', 'MAX_ADDR', 'NULL', 'K', 'M', 1343 'NextEthernetAddr', 1344 'instantiate'] 1345