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