util: added line length and boolean comparison style checkers
[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 import argparse
49 import ConfigParser
50 import inspect
51 import json
52 import re
53 import sys
54
55 import m5
56 import m5.ticks as ticks
57
58 sim_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
66 def no_parser(cls, flags, param):
67 raise Exception('Can\'t parse string: %s for parameter'
68 ' class: %s' % (str(param), cls.__name__))
69
70 def 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):
76 def 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
83 def 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
88 def 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
98 param_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
116 for name, parser in param_parsers.iteritems():
117 setattr(m5.params.__dict__[name], 'parse_ini', classmethod(parser))
118
119 class 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
146 def 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
153 class 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
363 class 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
396 class 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
434 class 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
509 parser = argparse.ArgumentParser()
510
511 parser.add_argument('config_file', metavar='config-file.ini',
512 help='.ini configuration file to load and run')
513
514 args = parser.parse_args(sys.argv[1:])
515
516 if args.config_file.endswith('.ini'):
517 config = ConfigIniFile()
518 config.load(args.config_file)
519 else:
520 config = ConfigJsonFile()
521 config.load(args.config_file)
522
523 ticks.fixGlobalFrequency()
524
525 mgr = ConfigManager(config)
526
527 mgr.find_all_objects()
528
529 m5.instantiate()
530
531 exit_event = m5.simulate()
532 print 'Exiting @ tick %i because %s' % (
533 m5.curTick(), exit_event.getCause())