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