1 # Copyright (c) 2014,2019 ARM Limited
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.
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.
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.
36 # Author: Andrew Bardsley
38 # This script allows .ini and .json system config file generated from a
39 # previous gem5 run to be read in and instantiated.
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
48 from __future__
import print_function
49 from __future__
import absolute_import
52 from six
.moves
import configparser
60 import m5
.ticks
as ticks
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
) }
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
73 def no_parser(cls
, flags
, param
):
74 raise Exception('Can\'t parse string: %s for parameter'
75 ' class: %s' % (str(param
), cls
.__name
__))
77 def simple_parser(suffix
='', cast
=lambda i
: i
):
78 def body(cls
, flags
, param
):
79 return cls(cast(param
+ suffix
))
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
):
86 ret
= cls(cast(str(param
) + 't'))
90 def addr_range_parser(cls
, flags
, param
):
92 _param
= param
.split(':')
93 (start
, end
) = _param
[0:2]
95 return m5
.objects
.AddrRange(start
=long(start
), end
=long(end
))
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
))
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
)
109 value
= ticks
.fromSeconds(value
)
110 return cls('%fB/s' % value
)
112 # These parameters have trickier parsing from .ini files than might be
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()
132 for name
, parser
in list(param_parsers
.items()):
133 setattr(m5
.params
.__dict
__[name
], 'parse_ini', classmethod(parser
))
135 class PortConnection(object):
136 """This class is similar to m5.params.PortRef but with just enough
137 information for ConfigManager"""
139 def __init__(self
, object_name
, port_name
, index
):
140 self
.object_name
= object_name
141 self
.port_name
= port_name
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:
153 return PortConnection(object_name
, port_name
, index
)
156 return '%s.%s[%d]' % (self
.object_name
, self
.port_name
, self
.index
)
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
))
163 """Convert any non list to a singleton list"""
164 if isinstance(v
, list):
169 class ConfigManager(object):
170 """Manager for parsing a Root configuration from a config file"""
171 def __init__(self
, config
):
173 self
.objects_by_name
= {}
174 self
.flags
= config
.get_flags()
176 def find_object(self
, object_name
):
177 """Find and configure (with just non-SimObject parameters)
180 if object_name
== 'Null':
183 if object_name
in self
.objects_by_name
:
184 return self
.objects_by_name
[object_name
]
186 object_type
= self
.config
.get_param(object_name
, 'type')
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
))
192 object_class
= sim_object_classes_by_name
[object_type
]
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
,
202 param_value
= [ param
.ptype
.parse_ini(self
.flags
, value
)
203 for value
in param_values
]
205 param_value
= param
.ptype
.parse_ini(
206 self
.flags
, self
.config
.get_param(object_name
,
209 parsed_params
[param_name
] = param_value
211 obj
= object_class(**parsed_params
)
212 self
.objects_by_name
[object_name
] = obj
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
221 if object_name
== 'Null':
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
,
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
])
235 param_value
= self
.config
.get_param(object_name
,
238 if param_value
!= 'Null':
239 setattr(obj
, param_name
, self
.objects_by_name
[
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"""
248 children
= self
.config
.get_object_children(object_name
)
250 for child_name
, child_paths
in children
:
251 param
= obj
.__class
__._params
.get(child_name
, None)
252 if child_name
== 'Null':
255 if isinstance(child_paths
, list):
256 child_list
= [ self
.objects_by_name
[path
]
257 for path
in child_paths
]
259 child_list
= self
.objects_by_name
[child_paths
]
261 obj
.add_child(child_name
, child_list
)
263 for path
in to_list(child_paths
):
264 self
.fill_in_children(path
, self
.objects_by_name
[path
])
268 def parse_port_name(self
, port
):
269 """Parse the name of a port"""
271 m
= re
.match('(.*)\.([^.\[]+)(\[(\d+)\])?', port
)
272 peer
, peer_port
, whole_index
, index
= m
.groups()
273 if index
is not None:
278 return (peer
, self
.objects_by_name
[peer
], peer_port
, index
)
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"""
285 if object_name
== 'Null':
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
)
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
)))
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"""
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
:
311 (from_port
.object_name
, from_port
.port_name
)] = 0
313 def port_has_correct_index(port
):
314 return port_bind_indices
[
315 (port
.object_name
, port
.port_name
)] == port
.index
317 def increment_port_index(port
):
319 (port
.object_name
, port
.port_name
)] += 1
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
335 if (port_has_correct_index(from_port
) and
336 port_has_correct_index(to_port
)):
338 connections_to_make
.append((from_port
, to_port
))
340 increment_port_index(from_port
)
341 increment_port_index(to_port
)
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')
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
]
353 setattr(from_object
, from_port
.port_name
,
354 getattr(to_object
, to_port
.port_name
))
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"""
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
)
365 # Add children to objects in the hierarchy from root
366 self
.fill_in_children('root', self
.find_object('root'))
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
)
374 # Gather a list of all port-to-port connections
376 for name
, obj
in list(self
.objects_by_name
.items()):
377 connections
+= self
.gather_port_connections(name
, obj
)
379 # Find an acceptable order to bind those port connections and
381 self
.bind_ports(connections
)
383 class ConfigFile(object):
387 def load(self
, config_file
):
388 """Load the named config file"""
391 def get_all_object_names(self
):
392 """Get a list of all the SimObject paths in the configuration"""
395 def get_param(self
, object_name
, param_name
):
396 """Get a single param or SimObject reference from the configuration
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"""
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
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"""
416 class ConfigIniFile(ConfigFile
):
418 self
.parser
= configparser
.ConfigParser()
420 def load(self
, config_file
):
421 self
.parser
.read(config_file
)
423 def get_all_object_names(self
):
424 return self
.parser
.sections()
426 def get_param(self
, object_name
, param_name
):
427 return self
.parser
.get(object_name
, param_name
)
429 def get_param_vector(self
, object_name
, param_name
):
430 return self
.parser
.get(object_name
, param_name
).split()
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()
439 def make_path(child_name
):
440 if object_name
== 'root':
443 return '%s.%s' % (object_name
, child_name
)
445 return [ (name
, make_path(name
)) for name
in child_names
]
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()
454 class ConfigJsonFile(ConfigFile
):
458 def is_sim_object(self
, node
):
459 return isinstance(node
, dict) and 'path' in node
461 def find_all_objects(self
, node
):
462 if self
.is_sim_object(node
):
463 self
.object_dicts
[node
['path']] = node
465 if isinstance(node
, list):
467 self
.find_all_objects(elem
)
468 elif isinstance(node
, dict):
469 for elem
in list(node
.values()):
470 self
.find_all_objects(elem
)
472 def load(self
, config_file
):
473 root
= json
.load(open(config_file
, 'r'))
474 self
.object_dicts
= {}
475 self
.find_all_objects(root
)
477 def get_all_object_names(self
):
478 return sorted(self
.object_dicts
.keys())
480 def parse_param_string(self
, node
):
483 elif self
.is_sim_object(node
):
488 def get_param(self
, object_name
, param_name
):
489 obj
= self
.object_dicts
[object_name
]
491 return self
.parse_param_string(obj
[param_name
])
493 def get_param_vector(self
, object_name
, param_name
):
494 obj
= self
.object_dicts
[object_name
]
496 return [ self
.parse_param_string(p
) for p
in obj
[param_name
] ]
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
]
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
]))
517 def get_port_peers(self
, object_name
, port_name
):
518 """Get the 'peer' element of any node with 'peer' and 'role'
520 obj
= self
.object_dicts
[object_name
]
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'])
529 parser
= argparse
.ArgumentParser()
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 '
537 args
= parser
.parse_args(sys
.argv
[1:])
539 if args
.config_file
.endswith('.ini'):
540 config
= ConfigIniFile()
541 config
.load(args
.config_file
)
543 config
= ConfigJsonFile()
544 config
.load(args
.config_file
)
546 ticks
.fixGlobalFrequency()
548 mgr
= ConfigManager(config
)
550 mgr
.find_all_objects()
552 m5
.instantiate(args
.checkpoint_dir
)
554 exit_event
= m5
.simulate()
555 print('Exiting @ tick %i because %s' % (m5
.curTick(), exit_event
.getCause()))