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    stats._bindStatHierarchy(root)
128    root.regStats()
129
130    # Do a fourth pass to initialize probe points
131    for obj in root.descendants(): obj.regProbePoints()
132
133    # Do a fifth pass to connect probe listeners
134    for obj in root.descendants(): obj.regProbeListeners()
135
136    # We want to generate the DVFS diagram for the system. This can only be
137    # done once all of the CPP objects have been created and initialised so
138    # that we are able to figure out which object belongs to which domain.
139    if options.dot_dvfs_config:
140        do_dvfs_dot(root, options.outdir, options.dot_dvfs_config)
141
142    # We're done registering statistics.  Enable the stats package now.
143    stats.enable()
144
145    # Restore checkpoint (if any)
146    if ckpt_dir:
147        _drain_manager.preCheckpointRestore()
148        ckpt = _m5.core.getCheckpoint(ckpt_dir)
149        _m5.core.unserializeGlobals(ckpt);
150        for obj in root.descendants(): obj.loadState(ckpt)
151    else:
152        for obj in root.descendants(): obj.initState()
153
154    # Check to see if any of the stat events are in the past after resuming from
155    # a checkpoint, If so, this call will shift them to be at a valid time.
156    updateStatEvents()
157
158need_startup = True
159def simulate(*args, **kwargs):
160    global need_startup
161
162    if need_startup:
163        root = objects.Root.getInstance()
164        for obj in root.descendants(): obj.startup()
165        need_startup = False
166
167        # Python exit handlers happen in reverse order.
168        # We want to dump stats last.
169        atexit.register(stats.dump)
170
171        # register our C++ exit callback function with Python
172        atexit.register(_m5.core.doExitCleanup)
173
174        # Reset to put the stats in a consistent state.
175        stats.reset()
176
177    if _drain_manager.isDrained():
178        _drain_manager.resume()
179
180    return _m5.event.simulate(*args, **kwargs)
181
182def drain():
183    """Drain the simulator in preparation of a checkpoint or memory mode
184    switch.
185
186    This operation is a no-op if the simulator is already in the
187    Drained state.
188
189    """
190
191    # Try to drain all objects. Draining might not be completed unless
192    # all objects return that they are drained on the first call. This
193    # is because as objects drain they may cause other objects to no
194    # longer be drained.
195    def _drain():
196        # Try to drain the system. The drain is successful if all
197        # objects are done without simulation. We need to simulate
198        # more if not.
199        if _drain_manager.tryDrain():
200            return True
201
202        # WARNING: if a valid exit event occurs while draining, it
203        # will not get returned to the user script
204        exit_event = _m5.event.simulate()
205        while exit_event.getCause() != 'Finished drain':
206            exit_event = simulate()
207
208        return False
209
210    # Don't try to drain a system that is already drained
211    is_drained = _drain_manager.isDrained()
212    while not is_drained:
213        is_drained = _drain()
214
215    assert _drain_manager.isDrained(), "Drain state inconsistent"
216
217def memWriteback(root):
218    for obj in root.descendants():
219        obj.memWriteback()
220
221def memInvalidate(root):
222    for obj in root.descendants():
223        obj.memInvalidate()
224
225def checkpoint(dir):
226    root = objects.Root.getInstance()
227    if not isinstance(root, objects.Root):
228        raise TypeError("Checkpoint must be called on a root object.")
229
230    drain()
231    memWriteback(root)
232    print("Writing checkpoint")
233    _m5.core.serializeAll(dir)
234
235def _changeMemoryMode(system, mode):
236    if not isinstance(system, (objects.Root, objects.System)):
237        raise TypeError("Parameter of type '%s'.  Must be type %s or %s." % \
238              (type(system), objects.Root, objects.System))
239    if system.getMemoryMode() != mode:
240        system.setMemoryMode(mode)
241    else:
242        print("System already in target mode. Memory mode unchanged.")
243
244def switchCpus(system, cpuList, verbose=True):
245    """Switch CPUs in a system.
246
247    Note: This method may switch the memory mode of the system if that
248    is required by the CPUs. It may also flush all caches in the
249    system.
250
251    Arguments:
252      system -- Simulated system.
253      cpuList -- (old_cpu, new_cpu) tuples
254    """
255
256    if verbose:
257        print("switching cpus")
258
259    if not isinstance(cpuList, list):
260        raise RuntimeError("Must pass a list to this function")
261    for item in cpuList:
262        if not isinstance(item, tuple) or len(item) != 2:
263            raise RuntimeError("List must have tuples of (oldCPU,newCPU)")
264
265    old_cpus = [old_cpu for old_cpu, new_cpu in cpuList]
266    new_cpus = [new_cpu for old_cpu, new_cpu in cpuList]
267    old_cpu_set = set(old_cpus)
268    memory_mode_name = new_cpus[0].memory_mode()
269    for old_cpu, new_cpu in cpuList:
270        if not isinstance(old_cpu, objects.BaseCPU):
271            raise TypeError("%s is not of type BaseCPU" % old_cpu)
272        if not isinstance(new_cpu, objects.BaseCPU):
273            raise TypeError("%s is not of type BaseCPU" % new_cpu)
274        if new_cpu in old_cpu_set:
275            raise RuntimeError(
276                "New CPU (%s) is in the list of old CPUs." % (old_cpu,))
277        if not new_cpu.switchedOut():
278            raise RuntimeError("New CPU (%s) is already active." % (new_cpu,))
279        if not new_cpu.support_take_over():
280            raise RuntimeError(
281                "New CPU (%s) does not support CPU handover." % (old_cpu,))
282        if new_cpu.memory_mode() != memory_mode_name:
283            raise RuntimeError(
284                "%s and %s require different memory modes." % (new_cpu,
285                                                               new_cpus[0]))
286        if old_cpu.switchedOut():
287            raise RuntimeError("Old CPU (%s) is inactive." % (new_cpu,))
288        if not old_cpu.support_take_over():
289            raise RuntimeError(
290                "Old CPU (%s) does not support CPU handover." % (old_cpu,))
291
292    try:
293        memory_mode = _memory_modes[memory_mode_name]
294    except KeyError:
295        raise RuntimeError("Invalid memory mode (%s)" % memory_mode_name)
296
297    drain()
298
299    # Now all of the CPUs are ready to be switched out
300    for old_cpu, new_cpu in cpuList:
301        old_cpu.switchOut()
302
303    # Change the memory mode if required. We check if this is needed
304    # to avoid printing a warning if no switch was performed.
305    if system.getMemoryMode() != memory_mode:
306        # Flush the memory system if we are switching to a memory mode
307        # that disables caches. This typically happens when switching to a
308        # hardware virtualized CPU.
309        if memory_mode == objects.params.atomic_noncaching:
310            memWriteback(system)
311            memInvalidate(system)
312
313        _changeMemoryMode(system, memory_mode)
314
315    for old_cpu, new_cpu in cpuList:
316        new_cpu.takeOverFrom(old_cpu)
317
318def notifyFork(root):
319    for obj in root.descendants():
320        obj.notifyFork()
321
322fork_count = 0
323def fork(simout="%(parent)s.f%(fork_seq)i"):
324    """Fork the simulator.
325
326    This function forks the simulator. After forking the simulator,
327    the child process gets its output files redirected to a new output
328    directory. The default name of the output directory is the same as
329    the parent with the suffix ".fN" added where N is the fork
330    sequence number. The name of the output directory can be
331    overridden using the simout keyword argument.
332
333    Output file formatting dictionary:
334      parent -- Path to the parent process's output directory.
335      fork_seq -- Fork sequence number.
336      pid -- PID of the child process.
337
338    Keyword Arguments:
339      simout -- New simulation output directory.
340
341    Return Value:
342      pid of the child process or 0 if running in the child.
343    """
344    from m5 import options
345    global fork_count
346
347    if not _m5.core.listenersDisabled():
348        raise RuntimeError("Can not fork a simulator with listeners enabled")
349
350    drain()
351
352    try:
353        pid = os.fork()
354    except OSError as e:
355        raise e
356
357    if pid == 0:
358        # In child, notify objects of the fork
359        root = objects.Root.getInstance()
360        notifyFork(root)
361        # Setup a new output directory
362        parent = options.outdir
363        options.outdir = simout % {
364                "parent" : parent,
365                "fork_seq" : fork_count,
366                "pid" : os.getpid(),
367                }
368        _m5.core.setOutputDir(options.outdir)
369    else:
370        fork_count += 1
371
372    return pid
373
374from _m5.core import disableAllListeners, listenersDisabled
375from _m5.core import listenersLoopbackOnly
376from _m5.core import curTick
377