Merge branch 'release-staging-v20.0.0.0' into develop
[gem5.git] / src / python / m5 / simulate.py
1 # Copyright (c) 2012,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 # Copyright (c) 2005 The Regents of The University of Michigan
14 # Copyright (c) 2010 Advanced Micro Devices, Inc.
15 # All rights reserved.
16 #
17 # Redistribution and use in source and binary forms, with or without
18 # modification, are permitted provided that the following conditions are
19 # met: redistributions of source code must retain the above copyright
20 # notice, this list of conditions and the following disclaimer;
21 # redistributions in binary form must reproduce the above copyright
22 # notice, this list of conditions and the following disclaimer in the
23 # documentation and/or other materials provided with the distribution;
24 # neither the name of the copyright holders nor the names of its
25 # contributors may be used to endorse or promote products derived from
26 # this software without specific prior written permission.
27 #
28 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
40 from __future__ import print_function
41
42 import atexit
43 import os
44 import sys
45
46 # import the wrapped C++ functions
47 import _m5.drain
48 import _m5.core
49 from _m5.stats import updateEvents as updateStatEvents
50
51 from . import stats
52 from . import SimObject
53 from . import ticks
54 from . import objects
55 from m5.util.dot_writer import do_dot, do_dvfs_dot
56 from m5.util.dot_writer_ruby import do_ruby_dot
57
58 from .util import fatal
59 from .util import attrdict
60
61 # define a MaxTick parameter, unsigned 64 bit
62 MaxTick = 2**64 - 1
63
64 _memory_modes = {
65 "atomic" : objects.params.atomic,
66 "timing" : objects.params.timing,
67 "atomic_noncaching" : objects.params.atomic_noncaching,
68 }
69
70 _drain_manager = _m5.drain.DrainManager.instance()
71
72 # The final hook to generate .ini files. Called from the user script
73 # once the config is built.
74 def instantiate(ckpt_dir=None):
75 from m5 import options
76
77 root = objects.Root.getInstance()
78
79 if not root:
80 fatal("Need to instantiate Root() before calling instantiate()")
81
82 # we need to fix the global frequency
83 ticks.fixGlobalFrequency()
84
85 # Make sure SimObject-valued params are in the configuration
86 # hierarchy so we catch them with future descendants() walks
87 for obj in root.descendants(): obj.adoptOrphanParams()
88
89 # Unproxy in sorted order for determinism
90 for obj in root.descendants(): obj.unproxyParams()
91
92 if options.dump_config:
93 ini_file = open(os.path.join(options.outdir, options.dump_config), 'w')
94 # Print ini sections in sorted order for easier diffing
95 for obj in sorted(root.descendants(), key=lambda o: o.path()):
96 obj.print_ini(ini_file)
97 ini_file.close()
98
99 if options.json_config:
100 try:
101 import json
102 json_file = open(
103 os.path.join(options.outdir, options.json_config), 'w')
104 d = root.get_config_as_dict()
105 json.dump(d, json_file, indent=4)
106 json_file.close()
107 except ImportError:
108 pass
109
110 if options.dot_config:
111 do_dot(root, options.outdir, options.dot_config)
112 do_ruby_dot(root, options.outdir, options.dot_config)
113
114 # Initialize the global statistics
115 stats.initSimStats()
116
117 # Create the C++ sim objects and connect ports
118 for obj in root.descendants(): obj.createCCObject()
119 for obj in root.descendants(): obj.connectPorts()
120
121 # Do a second pass to finish initializing the sim objects
122 for obj in root.descendants(): obj.init()
123
124 # Do a third pass to initialize statistics
125 stats._bindStatHierarchy(root)
126 root.regStats()
127
128 # Do a fourth pass to initialize probe points
129 for obj in root.descendants(): obj.regProbePoints()
130
131 # Do a fifth pass to connect probe listeners
132 for obj in root.descendants(): obj.regProbeListeners()
133
134 # We want to generate the DVFS diagram for the system. This can only be
135 # done once all of the CPP objects have been created and initialised so
136 # that we are able to figure out which object belongs to which domain.
137 if options.dot_dvfs_config:
138 do_dvfs_dot(root, options.outdir, options.dot_dvfs_config)
139
140 # We're done registering statistics. Enable the stats package now.
141 stats.enable()
142
143 # Restore checkpoint (if any)
144 if ckpt_dir:
145 _drain_manager.preCheckpointRestore()
146 ckpt = _m5.core.getCheckpoint(ckpt_dir)
147 _m5.core.unserializeGlobals(ckpt);
148 for obj in root.descendants(): obj.loadState(ckpt)
149 else:
150 for obj in root.descendants(): obj.initState()
151
152 # Check to see if any of the stat events are in the past after resuming from
153 # a checkpoint, If so, this call will shift them to be at a valid time.
154 updateStatEvents()
155
156 need_startup = True
157 def simulate(*args, **kwargs):
158 global need_startup
159
160 if need_startup:
161 root = objects.Root.getInstance()
162 for obj in root.descendants(): obj.startup()
163 need_startup = False
164
165 # Python exit handlers happen in reverse order.
166 # We want to dump stats last.
167 atexit.register(stats.dump)
168
169 # register our C++ exit callback function with Python
170 atexit.register(_m5.core.doExitCleanup)
171
172 # Reset to put the stats in a consistent state.
173 stats.reset()
174
175 if _drain_manager.isDrained():
176 _drain_manager.resume()
177
178 return _m5.event.simulate(*args, **kwargs)
179
180 def drain():
181 """Drain the simulator in preparation of a checkpoint or memory mode
182 switch.
183
184 This operation is a no-op if the simulator is already in the
185 Drained state.
186
187 """
188
189 # Try to drain all objects. Draining might not be completed unless
190 # all objects return that they are drained on the first call. This
191 # is because as objects drain they may cause other objects to no
192 # longer be drained.
193 def _drain():
194 # Try to drain the system. The drain is successful if all
195 # objects are done without simulation. We need to simulate
196 # more if not.
197 if _drain_manager.tryDrain():
198 return True
199
200 # WARNING: if a valid exit event occurs while draining, it
201 # will not get returned to the user script
202 exit_event = _m5.event.simulate()
203 while exit_event.getCause() != 'Finished drain':
204 exit_event = simulate()
205
206 return False
207
208 # Don't try to drain a system that is already drained
209 is_drained = _drain_manager.isDrained()
210 while not is_drained:
211 is_drained = _drain()
212
213 assert _drain_manager.isDrained(), "Drain state inconsistent"
214
215 def memWriteback(root):
216 for obj in root.descendants():
217 obj.memWriteback()
218
219 def memInvalidate(root):
220 for obj in root.descendants():
221 obj.memInvalidate()
222
223 def checkpoint(dir):
224 root = objects.Root.getInstance()
225 if not isinstance(root, objects.Root):
226 raise TypeError("Checkpoint must be called on a root object.")
227
228 drain()
229 memWriteback(root)
230 print("Writing checkpoint")
231 _m5.core.serializeAll(dir)
232
233 def _changeMemoryMode(system, mode):
234 if not isinstance(system, (objects.Root, objects.System)):
235 raise TypeError("Parameter of type '%s'. Must be type %s or %s." % \
236 (type(system), objects.Root, objects.System))
237 if system.getMemoryMode() != mode:
238 system.setMemoryMode(mode)
239 else:
240 print("System already in target mode. Memory mode unchanged.")
241
242 def switchCpus(system, cpuList, verbose=True):
243 """Switch CPUs in a system.
244
245 Note: This method may switch the memory mode of the system if that
246 is required by the CPUs. It may also flush all caches in the
247 system.
248
249 Arguments:
250 system -- Simulated system.
251 cpuList -- (old_cpu, new_cpu) tuples
252 """
253
254 if verbose:
255 print("switching cpus")
256
257 if not isinstance(cpuList, list):
258 raise RuntimeError("Must pass a list to this function")
259 for item in cpuList:
260 if not isinstance(item, tuple) or len(item) != 2:
261 raise RuntimeError("List must have tuples of (oldCPU,newCPU)")
262
263 old_cpus = [old_cpu for old_cpu, new_cpu in cpuList]
264 new_cpus = [new_cpu for old_cpu, new_cpu in cpuList]
265 old_cpu_set = set(old_cpus)
266 memory_mode_name = new_cpus[0].memory_mode()
267 for old_cpu, new_cpu in cpuList:
268 if not isinstance(old_cpu, objects.BaseCPU):
269 raise TypeError("%s is not of type BaseCPU" % old_cpu)
270 if not isinstance(new_cpu, objects.BaseCPU):
271 raise TypeError("%s is not of type BaseCPU" % new_cpu)
272 if new_cpu in old_cpu_set:
273 raise RuntimeError(
274 "New CPU (%s) is in the list of old CPUs." % (old_cpu,))
275 if not new_cpu.switchedOut():
276 raise RuntimeError("New CPU (%s) is already active." % (new_cpu,))
277 if not new_cpu.support_take_over():
278 raise RuntimeError(
279 "New CPU (%s) does not support CPU handover." % (old_cpu,))
280 if new_cpu.memory_mode() != memory_mode_name:
281 raise RuntimeError(
282 "%s and %s require different memory modes." % (new_cpu,
283 new_cpus[0]))
284 if old_cpu.switchedOut():
285 raise RuntimeError("Old CPU (%s) is inactive." % (new_cpu,))
286 if not old_cpu.support_take_over():
287 raise RuntimeError(
288 "Old CPU (%s) does not support CPU handover." % (old_cpu,))
289
290 try:
291 memory_mode = _memory_modes[memory_mode_name]
292 except KeyError:
293 raise RuntimeError("Invalid memory mode (%s)" % memory_mode_name)
294
295 drain()
296
297 # Now all of the CPUs are ready to be switched out
298 for old_cpu, new_cpu in cpuList:
299 old_cpu.switchOut()
300
301 # Change the memory mode if required. We check if this is needed
302 # to avoid printing a warning if no switch was performed.
303 if system.getMemoryMode() != memory_mode:
304 # Flush the memory system if we are switching to a memory mode
305 # that disables caches. This typically happens when switching to a
306 # hardware virtualized CPU.
307 if memory_mode == objects.params.atomic_noncaching:
308 memWriteback(system)
309 memInvalidate(system)
310
311 _changeMemoryMode(system, memory_mode)
312
313 for old_cpu, new_cpu in cpuList:
314 new_cpu.takeOverFrom(old_cpu)
315
316 def notifyFork(root):
317 for obj in root.descendants():
318 obj.notifyFork()
319
320 fork_count = 0
321 def fork(simout="%(parent)s.f%(fork_seq)i"):
322 """Fork the simulator.
323
324 This function forks the simulator. After forking the simulator,
325 the child process gets its output files redirected to a new output
326 directory. The default name of the output directory is the same as
327 the parent with the suffix ".fN" added where N is the fork
328 sequence number. The name of the output directory can be
329 overridden using the simout keyword argument.
330
331 Output file formatting dictionary:
332 parent -- Path to the parent process's output directory.
333 fork_seq -- Fork sequence number.
334 pid -- PID of the child process.
335
336 Keyword Arguments:
337 simout -- New simulation output directory.
338
339 Return Value:
340 pid of the child process or 0 if running in the child.
341 """
342 from m5 import options
343 global fork_count
344
345 if not _m5.core.listenersDisabled():
346 raise RuntimeError("Can not fork a simulator with listeners enabled")
347
348 drain()
349
350 try:
351 pid = os.fork()
352 except OSError as e:
353 raise e
354
355 if pid == 0:
356 # In child, notify objects of the fork
357 root = objects.Root.getInstance()
358 notifyFork(root)
359 # Setup a new output directory
360 parent = options.outdir
361 options.outdir = simout % {
362 "parent" : parent,
363 "fork_seq" : fork_count,
364 "pid" : os.getpid(),
365 }
366 _m5.core.setOutputDir(options.outdir)
367 else:
368 fork_count += 1
369
370 return pid
371
372 from _m5.core import disableAllListeners, listenersDisabled
373 from _m5.core import listenersLoopbackOnly
374 from _m5.core import curTick