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