read_config.py revision 11228
1# Copyright (c) 2014 ARM Limited 2# All rights reserved. 3# 4# The license below extends only to copyright in the software and shall 5# not be construed as granting a license to any other intellectual 6# property including but not limited to intellectual property relating 7# to a hardware implementation of the functionality of the software 8# licensed hereunder. You may use the software subject to the license 9# terms below provided that you ensure that this notice is replicated 10# unmodified and in its entirety in all distributions of the software, 11# modified or unmodified, in source code or in binary form. 12# 13# Redistribution and use in source and binary forms, with or without 14# modification, are permitted provided that the following conditions are 15# met: redistributions of source code must retain the above copyright 16# notice, this list of conditions and the following disclaimer; 17# redistributions in binary form must reproduce the above copyright 18# notice, this list of conditions and the following disclaimer in the 19# documentation and/or other materials provided with the distribution; 20# neither the name of the copyright holders nor the names of its 21# contributors may be used to endorse or promote products derived from 22# this software without specific prior written permission. 23# 24# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 28# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35# 36# Author: Andrew Bardsley 37 38# This script allows .ini and .json system config file generated from a 39# previous gem5 run to be read in and instantiated. 40# 41# This may be useful as a way of allowing variant run scripts (say, 42# with more complicated than usual checkpointing/stats dumping/ 43# simulation control) to read pre-described systems from config scripts 44# with better system-description capabilities. Splitting scripts 45# between system construction and run control may allow better 46# debugging. 47 48import argparse 49import ConfigParser 50import inspect 51import json 52import re 53import sys 54 55import m5 56import m5.ticks as ticks 57 58sim_object_classes_by_name = { 59 cls.__name__: cls for cls in m5.objects.__dict__.itervalues() 60 if inspect.isclass(cls) and issubclass(cls, m5.objects.SimObject) } 61 62# Add some parsing functions to Param classes to handle reading in .ini 63# file elements. This could be moved into src/python/m5/params.py if 64# reading .ini files from Python proves to be useful 65 66def no_parser(cls, flags, param): 67 raise Exception('Can\'t parse string: %s for parameter' 68 ' class: %s' % (str(param), cls.__name__)) 69 70def simple_parser(suffix='', cast=lambda i: i): 71 def body(cls, flags, param): 72 return cls(cast(param + suffix)) 73 return body 74 75# def tick_parser(cast=m5.objects.Latency): # lambda i: i): 76def tick_parser(cast=lambda i: i): 77 def body(cls, flags, param): 78 old_param = param 79 ret = cls(cast(str(param) + 't')) 80 return ret 81 return body 82 83def addr_range_parser(cls, flags, param): 84 sys.stdout.flush() 85 low, high = param.split(':') 86 return m5.objects.AddrRange(long(low), long(high)) 87 88def memory_bandwidth_parser(cls, flags, param): 89 # The string will be in tick/byte 90 # Convert to byte/tick 91 value = 1.0 / float(param) 92 # Convert to byte/s 93 value = ticks.fromSeconds(value) 94 return cls('%fB/s' % value) 95 96# These parameters have trickier parsing from .ini files than might be 97# expected 98param_parsers = { 99 'Bool': simple_parser(), 100 'ParamValue': no_parser, 101 'NumericParamValue': simple_parser(cast=long), 102 'TickParamValue': tick_parser(), 103 'Frequency': tick_parser(cast=m5.objects.Latency), 104 'Current': simple_parser(suffix='A'), 105 'Voltage': simple_parser(suffix='V'), 106 'Enum': simple_parser(), 107 'MemorySize': simple_parser(suffix='B'), 108 'MemorySize32': simple_parser(suffix='B'), 109 'AddrRange': addr_range_parser, 110 'String': simple_parser(), 111 'MemoryBandwidth': memory_bandwidth_parser, 112 'Time': simple_parser(), 113 'EthernetAddr': simple_parser() 114 } 115 116for name, parser in param_parsers.iteritems(): 117 setattr(m5.params.__dict__[name], 'parse_ini', classmethod(parser)) 118 119class PortConnection(object): 120 """This class is similar to m5.params.PortRef but with just enough 121 information for ConfigManager""" 122 123 def __init__(self, object_name, port_name, index): 124 self.object_name = object_name 125 self.port_name = port_name 126 self.index = index 127 128 @classmethod 129 def from_string(cls, str): 130 m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', str) 131 object_name, port_name, whole_index, index = m.groups() 132 if index is not None: 133 index = int(index) 134 else: 135 index = 0 136 137 return PortConnection(object_name, port_name, index) 138 139 def __str__(self): 140 return '%s.%s[%d]' % (self.object_name, self.port_name, self.index) 141 142 def __cmp__(self, right): 143 return cmp((self.object_name, self.port_name, self.index), 144 (right.object_name, right.port_name, right.index)) 145 146def to_list(v): 147 """Convert any non list to a singleton list""" 148 if isinstance(v, list): 149 return v 150 else: 151 return [v] 152 153class ConfigManager(object): 154 """Manager for parsing a Root configuration from a config file""" 155 def __init__(self, config): 156 self.config = config 157 self.objects_by_name = {} 158 self.flags = config.get_flags() 159 160 def find_object(self, object_name): 161 """Find and configure (with just non-SimObject parameters) 162 a single object""" 163 164 if object_name == 'Null': 165 return NULL 166 167 if object_name in self.objects_by_name: 168 return self.objects_by_name[object_name] 169 170 object_type = self.config.get_param(object_name, 'type') 171 172 if object_type not in sim_object_classes_by_name: 173 raise Exception('No SimObject type %s is available to' 174 ' build: %s' % (object_type, object_name)) 175 176 object_class = sim_object_classes_by_name[object_type] 177 178 parsed_params = {} 179 180 for param_name, param in object_class._params.iteritems(): 181 if issubclass(param.ptype, m5.params.ParamValue): 182 if isinstance(param, m5.params.VectorParamDesc): 183 param_values = self.config.get_param_vector(object_name, 184 param_name) 185 186 param_value = [ param.ptype.parse_ini(self.flags, value) 187 for value in param_values ] 188 else: 189 param_value = param.ptype.parse_ini( 190 self.flags, self.config.get_param(object_name, 191 param_name)) 192 193 parsed_params[param_name] = param_value 194 195 obj = object_class(**parsed_params) 196 self.objects_by_name[object_name] = obj 197 198 return obj 199 200 def fill_in_simobj_parameters(self, object_name, obj): 201 """Fill in all references to other SimObjects in an objects 202 parameters. This relies on all referenced objects having been 203 created""" 204 205 if object_name == 'Null': 206 return NULL 207 208 for param_name, param in obj.__class__._params.iteritems(): 209 if issubclass(param.ptype, m5.objects.SimObject): 210 if isinstance(param, m5.params.VectorParamDesc): 211 param_values = self.config.get_param_vector(object_name, 212 param_name) 213 214 setattr(obj, param_name, [ self.objects_by_name[name] 215 for name in param_values ]) 216 else: 217 param_value = self.config.get_param(object_name, 218 param_name) 219 220 if param_value != 'Null': 221 setattr(obj, param_name, self.objects_by_name[ 222 param_value]) 223 224 return obj 225 226 def fill_in_children(self, object_name, obj): 227 """Fill in the children of this object. This relies on all the 228 referenced objects having been created""" 229 230 children = self.config.get_object_children(object_name) 231 232 for child_name, child_paths in children: 233 param = obj.__class__._params.get(child_name, None) 234 235 if isinstance(child_paths, list): 236 child_list = [ self.objects_by_name[path] 237 for path in child_paths ] 238 else: 239 child_list = self.objects_by_name[child_paths] 240 241 obj.add_child(child_name, child_list) 242 243 for path in to_list(child_paths): 244 self.fill_in_children(path, self.objects_by_name[path]) 245 246 return obj 247 248 def parse_port_name(self, port): 249 """Parse the name of a port""" 250 251 m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', port) 252 peer, peer_port, whole_index, index = m.groups() 253 if index is not None: 254 index = int(index) 255 else: 256 index = 0 257 258 return (peer, self.objects_by_name[peer], peer_port, index) 259 260 def gather_port_connections(self, object_name, obj): 261 """Gather all the port-to-port connections from the named object. 262 Returns a list of (PortConnection, PortConnection) with unordered 263 (wrt. master/slave) connection information""" 264 265 if object_name == 'Null': 266 return NULL 267 268 parsed_ports = [] 269 for port_name, port in obj.__class__._ports.iteritems(): 270 # Assume that unnamed ports are unconnected 271 peers = self.config.get_port_peers(object_name, port_name) 272 273 for index, peer in zip(xrange(0, len(peers)), peers): 274 parsed_ports.append(( 275 PortConnection(object_name, port.name, index), 276 PortConnection.from_string(peer))) 277 278 return parsed_ports 279 280 def bind_ports(self, connections): 281 """Bind all ports from the given connection list. Note that the 282 connection list *must* list all connections with both (slave,master) 283 and (master,slave) orderings""" 284 285 # Markup a dict of how many connections are made to each port. 286 # This will be used to check that the next-to-be-made connection 287 # has a suitable port index 288 port_bind_indices = {} 289 for from_port, to_port in connections: 290 port_bind_indices[ 291 (from_port.object_name, from_port.port_name)] = 0 292 293 def port_has_correct_index(port): 294 return port_bind_indices[ 295 (port.object_name, port.port_name)] == port.index 296 297 def increment_port_index(port): 298 port_bind_indices[ 299 (port.object_name, port.port_name)] += 1 300 301 # Step through the sorted connections. Exactly one of 302 # each (slave,master) and (master,slave) pairs will be 303 # bindable because the connections are sorted. 304 # For example: port_bind_indices 305 # left right left right 306 # a.b[0] -> d.f[1] 0 0 X 307 # a.b[1] -> e.g 0 0 BIND! 308 # e.g -> a.b[1] 1 X 0 309 # d.f[0] -> f.h 0 0 BIND! 310 # d.f[1] -> a.b[0] 1 0 BIND! 311 connections_to_make = [] 312 for connection in sorted(connections): 313 from_port, to_port = connection 314 315 if (port_has_correct_index(from_port) and 316 port_has_correct_index(to_port)): 317 318 connections_to_make.append((from_port, to_port)) 319 320 increment_port_index(from_port) 321 increment_port_index(to_port) 322 323 # Exactly half of the connections (ie. all of them, one per 324 # direction) must now have been made 325 if (len(connections_to_make) * 2) != len(connections): 326 raise Exception('Port bindings can\'t be ordered') 327 328 # Actually do the binding 329 for from_port, to_port in connections_to_make: 330 from_object = self.objects_by_name[from_port.object_name] 331 to_object = self.objects_by_name[to_port.object_name] 332 333 setattr(from_object, from_port.port_name, 334 getattr(to_object, to_port.port_name)) 335 336 def find_all_objects(self): 337 """Find and build all SimObjects from the config file and connect 338 their ports together as described. Does not instantiate system""" 339 340 # Build SimObjects for all sections of the config file 341 # populating not-SimObject-valued parameters 342 for object_name in self.config.get_all_object_names(): 343 self.find_object(object_name) 344 345 # Add children to objects in the hierarchy from root 346 self.fill_in_children('root', self.find_object('root')) 347 348 # Now fill in SimObject-valued parameters in the knowledge that 349 # this won't be interpreted as becoming the parent of objects 350 # which are already in the root hierarchy 351 for name, obj in self.objects_by_name.iteritems(): 352 self.fill_in_simobj_parameters(name, obj) 353 354 # Gather a list of all port-to-port connections 355 connections = [] 356 for name, obj in self.objects_by_name.iteritems(): 357 connections += self.gather_port_connections(name, obj) 358 359 # Find an acceptable order to bind those port connections and 360 # bind them 361 self.bind_ports(connections) 362 363class ConfigFile(object): 364 def get_flags(self): 365 return set() 366 367 def load(self, config_file): 368 """Load the named config file""" 369 pass 370 371 def get_all_object_names(self): 372 """Get a list of all the SimObject paths in the configuration""" 373 pass 374 375 def get_param(self, object_name, param_name): 376 """Get a single param or SimObject reference from the configuration 377 as a string""" 378 pass 379 380 def get_param_vector(self, object_name, param_name): 381 """Get a vector param or vector of SimObject references from the 382 configuration as a list of strings""" 383 pass 384 385 def get_object_children(self, object_name): 386 """Get a list of (name, paths) for each child of this object. 387 paths is either a single string object path or a list of object 388 paths""" 389 pass 390 391 def get_port_peers(self, object_name, port_name): 392 """Get the list of connected port names (in the string form 393 object.port(\[index\])?) of the port object_name.port_name""" 394 pass 395 396class ConfigIniFile(ConfigFile): 397 def __init__(self): 398 self.parser = ConfigParser.ConfigParser() 399 400 def load(self, config_file): 401 self.parser.read(config_file) 402 403 def get_all_object_names(self): 404 return self.parser.sections() 405 406 def get_param(self, object_name, param_name): 407 return self.parser.get(object_name, param_name) 408 409 def get_param_vector(self, object_name, param_name): 410 return self.parser.get(object_name, param_name).split() 411 412 def get_object_children(self, object_name): 413 if self.parser.has_option(object_name, 'children'): 414 children = self.parser.get(object_name, 'children') 415 child_names = children.split() 416 else: 417 child_names = [] 418 419 def make_path(child_name): 420 if object_name == 'root': 421 return child_name 422 else: 423 return '%s.%s' % (object_name, child_name) 424 425 return [ (name, make_path(name)) for name in child_names ] 426 427 def get_port_peers(self, object_name, port_name): 428 if self.parser.has_option(object_name, port_name): 429 peer_string = self.parser.get(object_name, port_name) 430 return peer_string.split() 431 else: 432 return [] 433 434class ConfigJsonFile(ConfigFile): 435 def __init__(self): 436 pass 437 438 def is_sim_object(self, node): 439 return isinstance(node, dict) and 'path' in node 440 441 def find_all_objects(self, node): 442 if self.is_sim_object(node): 443 self.object_dicts[node['path']] = node 444 445 if isinstance(node, list): 446 for elem in node: 447 self.find_all_objects(elem) 448 elif isinstance(node, dict): 449 for elem in node.itervalues(): 450 self.find_all_objects(elem) 451 452 def load(self, config_file): 453 root = json.load(open(config_file, 'r')) 454 self.object_dicts = {} 455 self.find_all_objects(root) 456 457 def get_all_object_names(self): 458 return sorted(self.object_dicts.keys()) 459 460 def parse_param_string(self, node): 461 if node is None: 462 return "Null" 463 elif self.is_sim_object(node): 464 return node['path'] 465 else: 466 return str(node) 467 468 def get_param(self, object_name, param_name): 469 obj = self.object_dicts[object_name] 470 471 return self.parse_param_string(obj[param_name]) 472 473 def get_param_vector(self, object_name, param_name): 474 obj = self.object_dicts[object_name] 475 476 return [ self.parse_param_string(p) for p in obj[param_name] ] 477 478 def get_object_children(self, object_name): 479 """It is difficult to tell which elements are children in the 480 JSON file as there is no explicit 'children' node. Take any 481 element which is a full SimObject description or a list of 482 SimObject descriptions. This will not work with a mixed list of 483 references and descriptions but that's a scenario that isn't 484 possible (very likely?) with gem5's binding/naming rules""" 485 obj = self.object_dicts[object_name] 486 487 children = [] 488 for name, node in obj.iteritems(): 489 if self.is_sim_object(node): 490 children.append((name, node['path'])) 491 elif isinstance(node, list) and node != [] and all([ 492 self.is_sim_object(e) for e in node ]): 493 children.append((name, [ e['path'] for e in node ])) 494 495 return children 496 497 def get_port_peers(self, object_name, port_name): 498 """Get the 'peer' element of any node with 'peer' and 'role' 499 elements""" 500 obj = self.object_dicts[object_name] 501 502 peers = [] 503 if port_name in obj and 'peer' in obj[port_name] and \ 504 'role' in obj[port_name]: 505 peers = to_list(obj[port_name]['peer']) 506 507 return peers 508 509parser = argparse.ArgumentParser() 510 511parser.add_argument('config_file', metavar='config-file.ini', 512 help='.ini configuration file to load and run') 513 514args = parser.parse_args(sys.argv[1:]) 515 516if args.config_file.endswith('.ini'): 517 config = ConfigIniFile() 518 config.load(args.config_file) 519else: 520 config = ConfigJsonFile() 521 config.load(args.config_file) 522 523ticks.fixGlobalFrequency() 524 525mgr = ConfigManager(config) 526 527mgr.find_all_objects() 528 529m5.instantiate() 530 531exit_event = m5.simulate() 532print 'Exiting @ tick %i because %s' % ( 533 m5.curTick(), exit_event.getCause()) 534