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