Merge branch 'release-staging-v20.0.0.0' into develop
[gem5.git] / configs / example / read_config.py
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
48 from __future__ import print_function
49 from __future__ import absolute_import
50
51 import argparse
52 from six.moves import configparser
53 import inspect
54 import json
55 import re
56 import six
57 import sys
58
59 import m5
60 import m5.ticks as ticks
61
62 if six.PY3:
63 long = int
64
65 sim_object_classes_by_name = {
66 cls.__name__: cls for cls in list(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
73 def no_parser(cls, flags, param):
74 raise Exception('Can\'t parse string: %s for parameter'
75 ' class: %s' % (str(param), cls.__name__))
76
77 def 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):
83 def 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
90 def 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
104 def 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
114 param_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
132 for name, parser in list(param_parsers.items()):
133 setattr(m5.params.__dict__[name], 'parse_ini', classmethod(parser))
134
135 class 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
162 def 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
169 class 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 list(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 list(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 list(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(list(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 list(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 list(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
383 class 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
416 class 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
454 class 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 list(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 list(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
529 parser = argparse.ArgumentParser()
530
531 parser.add_argument('config_file', metavar='config-file.ini',
532 help='.ini configuration file to load and run')
533 parser.add_argument('--checkpoint-dir', type=str, default=None,
534 help='A checkpoint to directory to restore when starting '
535 'the simulation')
536
537 args = parser.parse_args(sys.argv[1:])
538
539 if args.config_file.endswith('.ini'):
540 config = ConfigIniFile()
541 config.load(args.config_file)
542 else:
543 config = ConfigJsonFile()
544 config.load(args.config_file)
545
546 ticks.fixGlobalFrequency()
547
548 mgr = ConfigManager(config)
549
550 mgr.find_all_objects()
551
552 m5.instantiate(args.checkpoint_dir)
553
554 exit_event = m5.simulate()
555 print('Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause()))