read_config.py revision 14050:09be542e022f
1# Copyright (c) 2014,2019 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 48from __future__ import print_function 49from __future__ import absolute_import 50 51import argparse 52import ConfigParser 53import inspect 54import json 55import re 56import six 57import sys 58 59import m5 60import m5.ticks as ticks 61 62if six.PY3: 63 long = int 64 65sim_object_classes_by_name = { 66 cls.__name__: cls for cls in m5.objects.__dict__.values() 67 if inspect.isclass(cls) and issubclass(cls, m5.objects.SimObject) } 68 69# Add some parsing functions to Param classes to handle reading in .ini 70# file elements. This could be moved into src/python/m5/params.py if 71# reading .ini files from Python proves to be useful 72 73def no_parser(cls, flags, param): 74 raise Exception('Can\'t parse string: %s for parameter' 75 ' class: %s' % (str(param), cls.__name__)) 76 77def simple_parser(suffix='', cast=lambda i: i): 78 def body(cls, flags, param): 79 return cls(cast(param + suffix)) 80 return body 81 82# def tick_parser(cast=m5.objects.Latency): # lambda i: i): 83def tick_parser(cast=lambda i: i): 84 def body(cls, flags, param): 85 old_param = param 86 ret = cls(cast(str(param) + 't')) 87 return ret 88 return body 89 90def addr_range_parser(cls, flags, param): 91 sys.stdout.flush() 92 (low, high, intlv_high_bit, xor_high_bit, 93 intlv_bits, intlv_match) = param.split(':') 94 return m5.objects.AddrRange( 95 start=long(low), end=long(high), 96 intlvHighBit=long(intlv_high_bit), xorHighBit=long(xor_high_bit), 97 intlvBits=long(intlv_bits), intlvMatch=long(intlv_match)) 98 99def memory_bandwidth_parser(cls, flags, param): 100 # The string will be in tick/byte 101 # Convert to byte/tick 102 value = 1.0 / float(param) 103 # Convert to byte/s 104 value = ticks.fromSeconds(value) 105 return cls('%fB/s' % value) 106 107# These parameters have trickier parsing from .ini files than might be 108# expected 109param_parsers = { 110 'Bool': simple_parser(), 111 'ParamValue': no_parser, 112 'NumericParamValue': simple_parser(cast=long), 113 'TickParamValue': tick_parser(), 114 'Frequency': tick_parser(cast=m5.objects.Latency), 115 'Current': simple_parser(suffix='A'), 116 'Voltage': simple_parser(suffix='V'), 117 'Enum': simple_parser(), 118 'MemorySize': simple_parser(suffix='B'), 119 'MemorySize32': simple_parser(suffix='B'), 120 'AddrRange': addr_range_parser, 121 'String': simple_parser(), 122 'MemoryBandwidth': memory_bandwidth_parser, 123 'Time': simple_parser(), 124 'EthernetAddr': simple_parser() 125 } 126 127for name, parser in param_parsers.items(): 128 setattr(m5.params.__dict__[name], 'parse_ini', classmethod(parser)) 129 130class PortConnection(object): 131 """This class is similar to m5.params.PortRef but with just enough 132 information for ConfigManager""" 133 134 def __init__(self, object_name, port_name, index): 135 self.object_name = object_name 136 self.port_name = port_name 137 self.index = index 138 139 @classmethod 140 def from_string(cls, str): 141 m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', str) 142 object_name, port_name, whole_index, index = m.groups() 143 if index is not None: 144 index = int(index) 145 else: 146 index = 0 147 148 return PortConnection(object_name, port_name, index) 149 150 def __str__(self): 151 return '%s.%s[%d]' % (self.object_name, self.port_name, self.index) 152 153 def __cmp__(self, right): 154 return cmp((self.object_name, self.port_name, self.index), 155 (right.object_name, right.port_name, right.index)) 156 157def to_list(v): 158 """Convert any non list to a singleton list""" 159 if isinstance(v, list): 160 return v 161 else: 162 return [v] 163 164class ConfigManager(object): 165 """Manager for parsing a Root configuration from a config file""" 166 def __init__(self, config): 167 self.config = config 168 self.objects_by_name = {} 169 self.flags = config.get_flags() 170 171 def find_object(self, object_name): 172 """Find and configure (with just non-SimObject parameters) 173 a single object""" 174 175 if object_name == 'Null': 176 return NULL 177 178 if object_name in self.objects_by_name: 179 return self.objects_by_name[object_name] 180 181 object_type = self.config.get_param(object_name, 'type') 182 183 if object_type not in sim_object_classes_by_name: 184 raise Exception('No SimObject type %s is available to' 185 ' build: %s' % (object_type, object_name)) 186 187 object_class = sim_object_classes_by_name[object_type] 188 189 parsed_params = {} 190 191 for param_name, param in object_class._params.items(): 192 if issubclass(param.ptype, m5.params.ParamValue): 193 if isinstance(param, m5.params.VectorParamDesc): 194 param_values = self.config.get_param_vector(object_name, 195 param_name) 196 197 param_value = [ param.ptype.parse_ini(self.flags, value) 198 for value in param_values ] 199 else: 200 param_value = param.ptype.parse_ini( 201 self.flags, self.config.get_param(object_name, 202 param_name)) 203 204 parsed_params[param_name] = param_value 205 206 obj = object_class(**parsed_params) 207 self.objects_by_name[object_name] = obj 208 209 return obj 210 211 def fill_in_simobj_parameters(self, object_name, obj): 212 """Fill in all references to other SimObjects in an objects 213 parameters. This relies on all referenced objects having been 214 created""" 215 216 if object_name == 'Null': 217 return NULL 218 219 for param_name, param in obj.__class__._params.items(): 220 if issubclass(param.ptype, m5.objects.SimObject): 221 if isinstance(param, m5.params.VectorParamDesc): 222 param_values = self.config.get_param_vector(object_name, 223 param_name) 224 225 setattr(obj, param_name, 226 [ self.objects_by_name[name] 227 if name != 'Null' else m5.params.NULL 228 for name in param_values ]) 229 else: 230 param_value = self.config.get_param(object_name, 231 param_name) 232 233 if param_value != 'Null': 234 setattr(obj, param_name, self.objects_by_name[ 235 param_value]) 236 237 return obj 238 239 def fill_in_children(self, object_name, obj): 240 """Fill in the children of this object. This relies on all the 241 referenced objects having been created""" 242 243 children = self.config.get_object_children(object_name) 244 245 for child_name, child_paths in children: 246 param = obj.__class__._params.get(child_name, None) 247 if child_name == 'Null': 248 continue 249 250 if isinstance(child_paths, list): 251 child_list = [ self.objects_by_name[path] 252 for path in child_paths ] 253 else: 254 child_list = self.objects_by_name[child_paths] 255 256 obj.add_child(child_name, child_list) 257 258 for path in to_list(child_paths): 259 self.fill_in_children(path, self.objects_by_name[path]) 260 261 return obj 262 263 def parse_port_name(self, port): 264 """Parse the name of a port""" 265 266 m = re.match('(.*)\.([^.\[]+)(\[(\d+)\])?', port) 267 peer, peer_port, whole_index, index = m.groups() 268 if index is not None: 269 index = int(index) 270 else: 271 index = 0 272 273 return (peer, self.objects_by_name[peer], peer_port, index) 274 275 def gather_port_connections(self, object_name, obj): 276 """Gather all the port-to-port connections from the named object. 277 Returns a list of (PortConnection, PortConnection) with unordered 278 (wrt. master/slave) connection information""" 279 280 if object_name == 'Null': 281 return NULL 282 283 parsed_ports = [] 284 for port_name, port in obj.__class__._ports.items(): 285 # Assume that unnamed ports are unconnected 286 peers = self.config.get_port_peers(object_name, port_name) 287 288 for index, peer in zip(range(0, len(peers)), peers): 289 parsed_ports.append(( 290 PortConnection(object_name, port.name, index), 291 PortConnection.from_string(peer))) 292 293 return parsed_ports 294 295 def bind_ports(self, connections): 296 """Bind all ports from the given connection list. Note that the 297 connection list *must* list all connections with both (slave,master) 298 and (master,slave) orderings""" 299 300 # Markup a dict of how many connections are made to each port. 301 # This will be used to check that the next-to-be-made connection 302 # has a suitable port index 303 port_bind_indices = {} 304 for from_port, to_port in connections: 305 port_bind_indices[ 306 (from_port.object_name, from_port.port_name)] = 0 307 308 def port_has_correct_index(port): 309 return port_bind_indices[ 310 (port.object_name, port.port_name)] == port.index 311 312 def increment_port_index(port): 313 port_bind_indices[ 314 (port.object_name, port.port_name)] += 1 315 316 # Step through the sorted connections. Exactly one of 317 # each (slave,master) and (master,slave) pairs will be 318 # bindable because the connections are sorted. 319 # For example: port_bind_indices 320 # left right left right 321 # a.b[0] -> d.f[1] 0 0 X 322 # a.b[1] -> e.g 0 0 BIND! 323 # e.g -> a.b[1] 1 X 0 324 # d.f[0] -> f.h 0 0 BIND! 325 # d.f[1] -> a.b[0] 1 0 BIND! 326 connections_to_make = [] 327 for connection in sorted(connections): 328 from_port, to_port = connection 329 330 if (port_has_correct_index(from_port) and 331 port_has_correct_index(to_port)): 332 333 connections_to_make.append((from_port, to_port)) 334 335 increment_port_index(from_port) 336 increment_port_index(to_port) 337 338 # Exactly half of the connections (ie. all of them, one per 339 # direction) must now have been made 340 if (len(connections_to_make) * 2) != len(connections): 341 raise Exception('Port bindings can\'t be ordered') 342 343 # Actually do the binding 344 for from_port, to_port in connections_to_make: 345 from_object = self.objects_by_name[from_port.object_name] 346 to_object = self.objects_by_name[to_port.object_name] 347 348 setattr(from_object, from_port.port_name, 349 getattr(to_object, to_port.port_name)) 350 351 def find_all_objects(self): 352 """Find and build all SimObjects from the config file and connect 353 their ports together as described. Does not instantiate system""" 354 355 # Build SimObjects for all sections of the config file 356 # populating not-SimObject-valued parameters 357 for object_name in self.config.get_all_object_names(): 358 self.find_object(object_name) 359 360 # Add children to objects in the hierarchy from root 361 self.fill_in_children('root', self.find_object('root')) 362 363 # Now fill in SimObject-valued parameters in the knowledge that 364 # this won't be interpreted as becoming the parent of objects 365 # which are already in the root hierarchy 366 for name, obj in self.objects_by_name.items(): 367 self.fill_in_simobj_parameters(name, obj) 368 369 # Gather a list of all port-to-port connections 370 connections = [] 371 for name, obj in self.objects_by_name.items(): 372 connections += self.gather_port_connections(name, obj) 373 374 # Find an acceptable order to bind those port connections and 375 # bind them 376 self.bind_ports(connections) 377 378class ConfigFile(object): 379 def get_flags(self): 380 return set() 381 382 def load(self, config_file): 383 """Load the named config file""" 384 pass 385 386 def get_all_object_names(self): 387 """Get a list of all the SimObject paths in the configuration""" 388 pass 389 390 def get_param(self, object_name, param_name): 391 """Get a single param or SimObject reference from the configuration 392 as a string""" 393 pass 394 395 def get_param_vector(self, object_name, param_name): 396 """Get a vector param or vector of SimObject references from the 397 configuration as a list of strings""" 398 pass 399 400 def get_object_children(self, object_name): 401 """Get a list of (name, paths) for each child of this object. 402 paths is either a single string object path or a list of object 403 paths""" 404 pass 405 406 def get_port_peers(self, object_name, port_name): 407 """Get the list of connected port names (in the string form 408 object.port(\[index\])?) of the port object_name.port_name""" 409 pass 410 411class ConfigIniFile(ConfigFile): 412 def __init__(self): 413 self.parser = ConfigParser.ConfigParser() 414 415 def load(self, config_file): 416 self.parser.read(config_file) 417 418 def get_all_object_names(self): 419 return self.parser.sections() 420 421 def get_param(self, object_name, param_name): 422 return self.parser.get(object_name, param_name) 423 424 def get_param_vector(self, object_name, param_name): 425 return self.parser.get(object_name, param_name).split() 426 427 def get_object_children(self, object_name): 428 if self.parser.has_option(object_name, 'children'): 429 children = self.parser.get(object_name, 'children') 430 child_names = children.split() 431 else: 432 child_names = [] 433 434 def make_path(child_name): 435 if object_name == 'root': 436 return child_name 437 else: 438 return '%s.%s' % (object_name, child_name) 439 440 return [ (name, make_path(name)) for name in child_names ] 441 442 def get_port_peers(self, object_name, port_name): 443 if self.parser.has_option(object_name, port_name): 444 peer_string = self.parser.get(object_name, port_name) 445 return peer_string.split() 446 else: 447 return [] 448 449class ConfigJsonFile(ConfigFile): 450 def __init__(self): 451 pass 452 453 def is_sim_object(self, node): 454 return isinstance(node, dict) and 'path' in node 455 456 def find_all_objects(self, node): 457 if self.is_sim_object(node): 458 self.object_dicts[node['path']] = node 459 460 if isinstance(node, list): 461 for elem in node: 462 self.find_all_objects(elem) 463 elif isinstance(node, dict): 464 for elem in node.values(): 465 self.find_all_objects(elem) 466 467 def load(self, config_file): 468 root = json.load(open(config_file, 'r')) 469 self.object_dicts = {} 470 self.find_all_objects(root) 471 472 def get_all_object_names(self): 473 return sorted(self.object_dicts.keys()) 474 475 def parse_param_string(self, node): 476 if node is None: 477 return "Null" 478 elif self.is_sim_object(node): 479 return node['path'] 480 else: 481 return str(node) 482 483 def get_param(self, object_name, param_name): 484 obj = self.object_dicts[object_name] 485 486 return self.parse_param_string(obj[param_name]) 487 488 def get_param_vector(self, object_name, param_name): 489 obj = self.object_dicts[object_name] 490 491 return [ self.parse_param_string(p) for p in obj[param_name] ] 492 493 def get_object_children(self, object_name): 494 """It is difficult to tell which elements are children in the 495 JSON file as there is no explicit 'children' node. Take any 496 element which is a full SimObject description or a list of 497 SimObject descriptions. This will not work with a mixed list of 498 references and descriptions but that's a scenario that isn't 499 possible (very likely?) with gem5's binding/naming rules""" 500 obj = self.object_dicts[object_name] 501 502 children = [] 503 for name, node in obj.items(): 504 if self.is_sim_object(node): 505 children.append((name, node['path'])) 506 elif isinstance(node, list) and node != [] and all([ 507 self.is_sim_object(e) for e in node ]): 508 children.append((name, [ e['path'] for e in node ])) 509 510 return children 511 512 def get_port_peers(self, object_name, port_name): 513 """Get the 'peer' element of any node with 'peer' and 'role' 514 elements""" 515 obj = self.object_dicts[object_name] 516 517 peers = [] 518 if port_name in obj and 'peer' in obj[port_name] and \ 519 'role' in obj[port_name]: 520 peers = to_list(obj[port_name]['peer']) 521 522 return peers 523 524parser = argparse.ArgumentParser() 525 526parser.add_argument('config_file', metavar='config-file.ini', 527 help='.ini configuration file to load and run') 528parser.add_argument('--checkpoint-dir', type=str, default=None, 529 help='A checkpoint to directory to restore when starting ' 530 'the simulation') 531 532args = parser.parse_args(sys.argv[1:]) 533 534if args.config_file.endswith('.ini'): 535 config = ConfigIniFile() 536 config.load(args.config_file) 537else: 538 config = ConfigJsonFile() 539 config.load(args.config_file) 540 541ticks.fixGlobalFrequency() 542 543mgr = ConfigManager(config) 544 545mgr.find_all_objects() 546 547m5.instantiate(args.checkpoint_dir) 548 549exit_event = m5.simulate() 550print('Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause())) 551