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