stdlib,configs,tests: Add gem5 MultiSim (MultiProcessing for gem5) (#1167)
This allows for multiple gem5 simulations to be spawned from a single
parent gem5 process, as defined in a simgle gem5 configuration. In this
design _all_ the `Simulator`s are defined in the simulation script and
then added to the mutlisim module. For example:
```py
from gem5.simulate.Simulator import Simulator
import gem5.utils.multisim as multisim
# Construct the board[0] and board[1] as you wish here...
simulator1 = Simulator(board=board[0], id="board-1")
simulator2 = Simulator(board=board[1], id="board-2")
multisim.add_simulator(simulator1)
multisim.add_simulator(simulator2)
```
This specifies that two simulations are to be run in parallel in
seperate threads: one specified by `simulator1` and another by
`simulator2`. They are then added to MultiSim via the
`multisim.add_simulator` function. The user can specify an id via the
Simulator constructor. This is used to give each process a unique id and
output directory name. Given this, the id should be a helpful name
describing the simulation being specified. If not specified one is
automatically given.
To run these simulators we use `<gem5 binary> -m gem5.utils.multisim
<script> -p <num_processes>`. Note: multisim is an executable module in
gem5. This is the same module we input into our scripts to add the
simulators. This is an intentionally modular encapsulated design. When
the module processes a script it will schedule multiple gem5 jobs and,
dependent on the number of processes specified, will create child gem5
processes to processes tjese jobs (jobs are just gem5 simulations in
this case). The `--processes` (`-p`) argument is optional and if not
specified the max number of processes which can be run concurrently will
be the number of available threads on the host system.
The id for each process is used to create a subdirectory inside the
`outputdor` (`m5out`) of that id name. E.g, in the example above the
ID's are `board-1` and `board-2`. Therefore the m5 out directory will
look as follows:
```sh
- m5out
- board-1
- stats.txt
- config.ini
- config.json
- terminal.out
- board-2
- stats.txt
- config.ini
- config.json
- terminal.out
```
Each simulations output is encapsulated inside the subdirectory of the
id name.
If the multisim configuation script is passed directly to gem5 (like a
traditional gem5 configuraiton script, i.e.: `<gem5 binary> <script>`),
the user may run a single simulation specified in that script by passing
its id as an argument. E.g. `<gem5 binary> <script> board-1` will run
the `board-1` simulation specified in `script`. If no argument is passed
an Exception is raised asking the user to either specify or use the
MultiSim module if multiprocessing is needed.
If the user desires a list of ids of the simulations specified in a
given MultiSim script, they can do so by passing the `--list` (`-l`)
parameter to the config script. I.e., `<gem5 binary> <script> --list`
will list all the IDs for all the simulations specified in`script`.
This change comes with two new example scripts found in
'configs/example/gem5_library/multsim" to demonstrate multisim in both
an SE and FS mode simulation. Tests have been added which run these
scripts as part of gem5' Daily suite of tests.
Notes
=====
* **Bug fixed**: The `NoCache` classic cache hierarchy has been modified
so the Xbar is no longet set with a `__func__` call. This interfered
with MultiProcessing as this structure is not serializable via Pickle.
This was quite bad design anyway so should be changed
* **Change**: `readfile_contents` parameter previously wrote its value
to a file called "readfile" in the output dorectory. This has been
changed to write to a file called "readfile_{hash}" with "{hash}" being
a hash of the `readfile_contents`. This ensures that, during multisim
running, this file is not overwritten by other processes.
* **Removal note**: This implementation supercedes the functionality
outlined in 'src/python/gem5/utils/multiprocessing'. As such, this code
has been removed.
Limitations/Things to Fix/Improve
=================================
* Though each Simulator process has its own output directory (a
subdirectory within m5out, with an ID set by the user unique to that
Simulator), the stdout and stderr are still output to the terminal, not
the output directory. This results in: 1. stdout and stderr data lost
and not recorded for these runs. 2. An incredibly noisy terminal output.
* Each process uses the same cached resources. While there are locks on
resources when downloading, each processes will hash the resources they
require to ensure they are valid. This is very inefficient in cases
where resources are common between processes (e.g., you may have 10
processes each using the same disk image with each processes hashing the
disk images independently to give the same result to validate the
resources).
Change-Id: Ief5a3b765070c622d1f0de53ebd545c85a3f0eee
---------
Signed-off-by: Jason Lowe-Power <jason@lowepower.com>
Co-authored-by: Jason Lowe-Power <jason@lowepower.com>
This commit is contained in:
@@ -107,8 +107,8 @@ board.set_se_binary_workload(
|
||||
|
||||
# Lastly we run the simulation.
|
||||
max_ticks = 10**6
|
||||
simulator = Simulator(board=board, full_system=False)
|
||||
simulator.run(max_ticks=max_ticks)
|
||||
simulator = Simulator(board=board, full_system=False, max_ticks=max_ticks)
|
||||
simulator.run()
|
||||
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
|
||||
138
configs/example/gem5_library/multisim/multisim-fs-x86-npb.py
Normal file
138
configs/example/gem5_library/multisim/multisim-fs-x86-npb.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# Copyright (c) 2024 The Regents of the University of California.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""An example of a single configuration script for defining multiple
|
||||
simulations through the gem5 `multisim` module.
|
||||
|
||||
This script creates 6 full system simulations by interating through a suite
|
||||
of benchmarks and different cores counts.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
1. To run all the simulations defined in this script::
|
||||
|
||||
```shell
|
||||
<gem5-binary> -m gem5.utils.multisim \
|
||||
configs/example/gem5_library/multisim/multisim-fs-x86-npb.py
|
||||
```
|
||||
|
||||
2. To run a specific simulation defined in this script:
|
||||
|
||||
```shell
|
||||
<gem5-binary> configs/example/gem5_library/multisim/multisim-fs-x86-npb.py \
|
||||
<process_id> # e.g. npb-bt-a_cores-1
|
||||
```
|
||||
|
||||
3. To list all the IDs of the simulations defined in this script:
|
||||
|
||||
```shell
|
||||
<gem5-binary> configs/example/gem5_library/multisim/multisim-fs-x86-npb.py -l
|
||||
```
|
||||
"""
|
||||
|
||||
import m5
|
||||
|
||||
import gem5.utils.multisim as multisim
|
||||
from gem5.coherence_protocol import CoherenceProtocol
|
||||
from gem5.components.boards.x86_board import X86Board
|
||||
from gem5.components.memory import DualChannelDDR4_2400
|
||||
from gem5.components.processors.cpu_types import CPUTypes
|
||||
from gem5.components.processors.simple_switchable_processor import (
|
||||
SimpleSwitchableProcessor,
|
||||
)
|
||||
from gem5.isas import ISA
|
||||
from gem5.resources.resource import obtain_resource
|
||||
from gem5.simulate.simulator import (
|
||||
ExitEvent,
|
||||
Simulator,
|
||||
)
|
||||
from gem5.utils.requires import requires
|
||||
|
||||
requires(
|
||||
isa_required=ISA.X86,
|
||||
coherence_protocol_required=CoherenceProtocol.MESI_TWO_LEVEL,
|
||||
)
|
||||
|
||||
from gem5.components.cachehierarchies.ruby.mesi_two_level_cache_hierarchy import (
|
||||
MESITwoLevelCacheHierarchy,
|
||||
)
|
||||
|
||||
|
||||
def handle_workbegin():
|
||||
m5.stats.reset()
|
||||
processor.switch()
|
||||
yield False
|
||||
|
||||
|
||||
def handle_workend():
|
||||
m5.stats.dump()
|
||||
yield True
|
||||
|
||||
|
||||
# Set the maximum number of concurrent processes to be 3.
|
||||
multisim.set_num_processes(3)
|
||||
|
||||
# Here we imagine an experiment wanting to run each NPB benchmark on the same
|
||||
# system twice: once with 1 core and once with 2 cores.
|
||||
for benchmark in obtain_resource("npb-benchmark-suite"):
|
||||
for num_cores in [1, 2]:
|
||||
cache_hierarchy = MESITwoLevelCacheHierarchy(
|
||||
l1d_size="32kB",
|
||||
l1i_size="32kB",
|
||||
l2_size="256kB",
|
||||
l1d_assoc=8,
|
||||
l1i_assoc=8,
|
||||
l2_assoc=16,
|
||||
num_l2_banks=2,
|
||||
)
|
||||
memory = DualChannelDDR4_2400(size="3GB")
|
||||
processor = SimpleSwitchableProcessor(
|
||||
starting_core_type=CPUTypes.ATOMIC,
|
||||
switch_core_type=CPUTypes.TIMING,
|
||||
isa=ISA.X86,
|
||||
num_cores=num_cores,
|
||||
)
|
||||
board = X86Board(
|
||||
clk_freq="3GHz",
|
||||
processor=processor,
|
||||
memory=memory,
|
||||
cache_hierarchy=cache_hierarchy,
|
||||
)
|
||||
|
||||
board.set_workload(benchmark)
|
||||
|
||||
simulator = Simulator(
|
||||
board=board,
|
||||
on_exit_event={
|
||||
ExitEvent.WORKBEGIN: handle_workbegin(),
|
||||
ExitEvent.WORKEND: handle_workend(),
|
||||
},
|
||||
)
|
||||
|
||||
simulator.set_id(f"{benchmark.get_id()}_cores-{num_cores}")
|
||||
|
||||
multisim.add_simulator(simulator)
|
||||
87
configs/example/gem5_library/multisim/multisim-print-this.py
Normal file
87
configs/example/gem5_library/multisim/multisim-print-this.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# Copyright (c) 2024 The Regents of the University of California
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""An example of a single configuration script for defining multiple
|
||||
simulations through the gem5 `multisim` module.
|
||||
|
||||
This script is very simple and simply prints a simple message once for each
|
||||
simulation, outputing the process id.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
1. To run all the simulations defined in this script::
|
||||
|
||||
```shell
|
||||
<gem5-binary> -m gem5.utils.multisim \
|
||||
configs/example/gem5_library/multisim/multisim-print-this.py
|
||||
```
|
||||
|
||||
2. To run a specific simulation defined in this script:
|
||||
|
||||
```shell
|
||||
<gem5-binary> configs/example/gem5_library/multisim/multisim-print-this.py \
|
||||
process_id_1
|
||||
```
|
||||
|
||||
3. To list all the IDs of the simulations defined in this script:
|
||||
|
||||
```shell
|
||||
<gem5-binary> configs/example/gem5_library/multisim/multisim-print-this.py -l
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
import gem5.utils.multisim as multisim
|
||||
from gem5.components.boards.simple_board import SimpleBoard
|
||||
from gem5.components.cachehierarchies.classic.no_cache import NoCache
|
||||
from gem5.components.memory import SingleChannelDDR3_1600
|
||||
from gem5.components.processors.cpu_types import CPUTypes
|
||||
from gem5.components.processors.simple_processor import SimpleProcessor
|
||||
from gem5.isas import ISA
|
||||
from gem5.resources.resource import obtain_resource
|
||||
from gem5.simulate.simulator import Simulator
|
||||
|
||||
# Set the maximum number of concurrent processes to be 2.
|
||||
multisim.set_num_processes(2)
|
||||
|
||||
for process_id in range(5):
|
||||
cache_hierarchy = NoCache()
|
||||
memory = SingleChannelDDR3_1600(size="32MB")
|
||||
processor = SimpleProcessor(
|
||||
cpu_type=CPUTypes.TIMING, isa=ISA.X86, num_cores=1
|
||||
)
|
||||
board = SimpleBoard(
|
||||
clk_freq="1GHz",
|
||||
processor=processor,
|
||||
memory=memory,
|
||||
cache_hierarchy=cache_hierarchy,
|
||||
)
|
||||
board.set_se_binary_workload(
|
||||
binary=obtain_resource("x86-print-this"),
|
||||
arguments=[f"Hello from process {process_id}", 1],
|
||||
)
|
||||
multisim.add_simulator(Simulator(board=board, id=f"process_{process_id}"))
|
||||
@@ -316,6 +316,9 @@ PySource('gem5.utils', 'gem5/utils/progress_bar.py')
|
||||
PySource('gem5.utils', 'gem5/utils/requires.py')
|
||||
PySource('gem5.utils',
|
||||
'gem5/utils/socks_ssl_context.py')
|
||||
PySource('gem5.utils.multisim', 'gem5/utils/multisim/__init__.py')
|
||||
PySource('gem5.utils.multisim', 'gem5/utils/multisim/multisim.py')
|
||||
PySource('gem5.utils.multisim', 'gem5/utils/multisim/__main__.py')
|
||||
PySource('gem5.utils.multiprocessing',
|
||||
'gem5/utils/multiprocessing/__init__.py')
|
||||
PySource('gem5.utils.multiprocessing',
|
||||
|
||||
@@ -215,7 +215,15 @@ class KernelDiskWorkload:
|
||||
if readfile:
|
||||
self.readfile = readfile
|
||||
elif readfile_contents:
|
||||
self.readfile = os.path.join(m5.options.outdir, "readfile")
|
||||
# We hash the contents of the readfile and append it to the
|
||||
# readfile name. This is to ensure that we don't overwrite the
|
||||
# readfile if the contents are different.
|
||||
readfile_contents_hash = hex(
|
||||
hash(tuple(bytes(readfile_contents, "utf-8")))
|
||||
)
|
||||
self.readfile = os.path.join(
|
||||
m5.options.outdir, ("readfile_" + readfile_contents_hash)
|
||||
)
|
||||
|
||||
# Add the contents to the readfile, if specified.
|
||||
if readfile_contents:
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from m5.objects import (
|
||||
BadAddr,
|
||||
BaseXBar,
|
||||
@@ -64,8 +66,7 @@ class NoCache(AbstractClassicCacheHierarchy):
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _get_default_membus() -> SystemXBar:
|
||||
def _get_default_membus(self) -> SystemXBar:
|
||||
"""
|
||||
A method used to obtain the default memory bus of 64 bit in width for
|
||||
the NoCache CacheHierarchy.
|
||||
@@ -82,18 +83,16 @@ class NoCache(AbstractClassicCacheHierarchy):
|
||||
membus.max_routing_table_size = 2048
|
||||
return membus
|
||||
|
||||
def __init__(
|
||||
self, membus: BaseXBar = _get_default_membus.__func__()
|
||||
) -> None:
|
||||
def __init__(self, membus: Optional[BaseXBar] = None) -> None:
|
||||
"""
|
||||
:param membus: The memory bus for this setup. This parameter is
|
||||
optional and will default toa 64 bit width SystemXBar
|
||||
is not specified.
|
||||
if not specified.
|
||||
|
||||
:type membus: BaseXBar
|
||||
"""
|
||||
super().__init__()
|
||||
self.membus = membus
|
||||
self.membus = membus if membus else self._get_default_membus()
|
||||
|
||||
@overrides(AbstractClassicCacheHierarchy)
|
||||
def get_mem_side_port(self) -> Port:
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from m5.objects import (
|
||||
BadAddr,
|
||||
BaseXBar,
|
||||
@@ -47,8 +49,7 @@ class PrivateL1CacheHierarchy(AbstractClassicCacheHierarchy):
|
||||
A cache setup where each core has a private L1 data and instruction Cache.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _get_default_membus() -> SystemXBar:
|
||||
def _get_default_membus(self) -> SystemXBar:
|
||||
"""
|
||||
A method used to obtain the default memory bus of 64 bit in width for
|
||||
the PrivateL1CacheHierarchy.
|
||||
@@ -65,7 +66,7 @@ class PrivateL1CacheHierarchy(AbstractClassicCacheHierarchy):
|
||||
self,
|
||||
l1d_size: str,
|
||||
l1i_size: str,
|
||||
membus: BaseXBar = _get_default_membus.__func__(),
|
||||
membus: Optional[BaseXBar] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param l1d_size: The size of the L1 Data Cache (e.g., "32kB").
|
||||
@@ -73,11 +74,12 @@ class PrivateL1CacheHierarchy(AbstractClassicCacheHierarchy):
|
||||
:param l1i_size: The size of the L1 Instruction Cache (e.g., "32kB").
|
||||
|
||||
:param membus: The memory bus. This parameter is optional parameter and
|
||||
will default to a 64 bit width SystemXBar is not specified.
|
||||
will default to a 64 bit width SystemXBar is not
|
||||
specified.
|
||||
"""
|
||||
|
||||
AbstractClassicCacheHierarchy.__init__(self=self)
|
||||
self.membus = membus
|
||||
self.membus = membus if membus else self._get_default_membus()
|
||||
self._l1d_size = l1d_size
|
||||
self._l1i_size = l1i_size
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from m5.objects import (
|
||||
BadAddr,
|
||||
BaseCPU,
|
||||
@@ -65,8 +67,7 @@ class PrivateL1PrivateL2CacheHierarchy(
|
||||
and a private L2 cache.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _get_default_membus() -> SystemXBar:
|
||||
def _get_default_membus(self) -> SystemXBar:
|
||||
"""
|
||||
A method used to obtain the default memory bus of 64 bit in width for
|
||||
the PrivateL1PrivateL2 CacheHierarchy.
|
||||
@@ -85,7 +86,7 @@ class PrivateL1PrivateL2CacheHierarchy(
|
||||
l1d_size: str,
|
||||
l1i_size: str,
|
||||
l2_size: str,
|
||||
membus: BaseXBar = _get_default_membus.__func__(),
|
||||
membus: Optional[BaseXBar] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param l1d_size: The size of the L1 Data Cache (e.g., "32kB").
|
||||
@@ -95,7 +96,8 @@ class PrivateL1PrivateL2CacheHierarchy(
|
||||
:param l2_size: The size of the L2 Cache (e.g., "256kB").
|
||||
|
||||
:param membus: The memory bus. This parameter is optional parameter and
|
||||
will default to a 64 bit width SystemXBar is not specified.
|
||||
will default to a 64 bit width SystemXBar is not
|
||||
specified.
|
||||
"""
|
||||
|
||||
AbstractClassicCacheHierarchy.__init__(self=self)
|
||||
@@ -109,7 +111,7 @@ class PrivateL1PrivateL2CacheHierarchy(
|
||||
l2_assoc=4,
|
||||
)
|
||||
|
||||
self.membus = membus
|
||||
self.membus = membus if membus else self._get_default_membus()
|
||||
|
||||
@overrides(AbstractClassicCacheHierarchy)
|
||||
def get_mem_side_port(self) -> Port:
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from m5.objects import (
|
||||
BadAddr,
|
||||
BaseXBar,
|
||||
@@ -54,8 +56,7 @@ class PrivateL1SharedL2CacheHierarchy(
|
||||
inclusive with respect to the split I/D L1 and MMU caches.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _get_default_membus() -> SystemXBar:
|
||||
def _get_default_membus(self) -> SystemXBar:
|
||||
"""
|
||||
A method used to obtain the default memory bus of 64 bit in width for
|
||||
the PrivateL1SharedL2 CacheHierarchy.
|
||||
@@ -78,7 +79,7 @@ class PrivateL1SharedL2CacheHierarchy(
|
||||
l1d_assoc: int = 8,
|
||||
l1i_assoc: int = 8,
|
||||
l2_assoc: int = 16,
|
||||
membus: BaseXBar = _get_default_membus.__func__(),
|
||||
membus: Optional[BaseXBar] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param l1d_size: The size of the L1 Data Cache (e.g., "32kB").
|
||||
@@ -88,7 +89,8 @@ class PrivateL1SharedL2CacheHierarchy(
|
||||
:param l1i_assoc: The associativity of the L1 Instruction Cache.
|
||||
:param l2_assoc: The associativity of the L2 Cache.
|
||||
:param membus: The memory bus. This parameter is optional parameter and
|
||||
will default to a 64 bit width SystemXBar is not specified.
|
||||
will default to a 64 bit width SystemXBar is not
|
||||
specified.
|
||||
"""
|
||||
|
||||
AbstractClassicCacheHierarchy.__init__(self=self)
|
||||
@@ -102,7 +104,7 @@ class PrivateL1SharedL2CacheHierarchy(
|
||||
l2_assoc=l2_assoc,
|
||||
)
|
||||
|
||||
self.membus = membus
|
||||
self.membus = membus if membus else self._get_default_membus()
|
||||
|
||||
@overrides(AbstractClassicCacheHierarchy)
|
||||
def get_mem_side_port(self) -> Port:
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Callable,
|
||||
@@ -106,6 +107,8 @@ class Simulator:
|
||||
] = None,
|
||||
expected_execution_order: Optional[List[ExitEvent]] = None,
|
||||
checkpoint_path: Optional[Path] = None,
|
||||
max_ticks: Optional[int] = m5.MaxTick,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param board: The board to be simulated.
|
||||
@@ -140,6 +143,22 @@ class Simulator:
|
||||
the path is ``None``. **This parameter is deprecated.
|
||||
Please set the checkpoint when setting the board's
|
||||
workload**.
|
||||
:param max_ticks: The maximum number of ticks to execute in the
|
||||
simulation run before exiting with a ``MAX_TICK``
|
||||
exit event. If not set this value is to `m5.MaxTick`,
|
||||
the last value allowed in the tick variable. At
|
||||
present this is an unsigned 64-bit integer, and
|
||||
herefore is set to 2^4-1. Prior to intialization,
|
||||
max tickks can also be set via the `set_max_ticks`
|
||||
function.
|
||||
:param id: An optional parameter specifying the ID of the simulation.
|
||||
This is particularly useful when running muliple simuations in
|
||||
parallel. The ID can be unique and descriptive of the simulation. If
|
||||
not set, the ID will be a hash of the instantiated system and
|
||||
Simulator configuration. Note, the latter means the ID only available
|
||||
after the Simulator has been instantiated. The ID can be obtained via
|
||||
the `get_id` method.
|
||||
|
||||
|
||||
``on_exit_event`` usage notes
|
||||
---------------------------
|
||||
@@ -272,6 +291,11 @@ class Simulator:
|
||||
|
||||
"""
|
||||
|
||||
self.set_max_ticks(max_ticks)
|
||||
|
||||
if id:
|
||||
self.set_id(id)
|
||||
|
||||
# We specify a dictionary here outlining the default behavior for each
|
||||
# exit event. Each exit event is mapped to a generator.
|
||||
self._default_on_exit_dict = {
|
||||
@@ -367,6 +391,66 @@ class Simulator:
|
||||
|
||||
self._checkpoint_path = checkpoint_path
|
||||
|
||||
def set_id(self, id: str) -> None:
|
||||
"""Set the ID of the simulator.
|
||||
|
||||
As, in the caae of multisim, this ID will be used to create an
|
||||
output subdirectory, there needs to be rules on what an ID can be.
|
||||
For now, this function encoures that IDs can only be alphanumeric
|
||||
characters with underscores and dashes. Uunderscores and dashes cannot
|
||||
be at the start or end of the ID and the ID must start with at least
|
||||
one letter.
|
||||
|
||||
:param id: The ID of the simulator.
|
||||
"""
|
||||
|
||||
if not id:
|
||||
raise ValueError("ID cannot be an empty string.")
|
||||
|
||||
if not id[0].isalpha():
|
||||
raise ValueError("ID must start with a letter.")
|
||||
|
||||
if not id[-1].isalnum():
|
||||
raise ValueError(
|
||||
"ID must end with a alphanumeric value (a digit "
|
||||
"or a character)."
|
||||
)
|
||||
|
||||
if not all(char.isalnum() or char in ["_", "-"] for char in id):
|
||||
raise ValueError(
|
||||
"ID can only contain alphanumeric characters, "
|
||||
"underscores, and dashes."
|
||||
)
|
||||
self._id = id
|
||||
|
||||
def get_id(self) -> Optional[str]:
|
||||
"""
|
||||
Returns the ID of the simulation. This is particularly useful when
|
||||
running multiple simulations in parallel. The ID can be unique and
|
||||
descriptive of the simulation. It is set via the contructor or the
|
||||
`set_id` function. None if not set by either.
|
||||
"""
|
||||
|
||||
if hasattr(self, "_id") and self._id:
|
||||
return self._id
|
||||
|
||||
return None
|
||||
|
||||
def set_max_ticks(self, max_tick: int) -> None:
|
||||
"""Set the absolute (not relative) maximum number of ticks to run the
|
||||
simulation for. This is the maximum number of ticks to run the
|
||||
simulation for before exiting with a ``MAX_TICK`` exit event.
|
||||
"""
|
||||
if max_tick > m5.MaxTick:
|
||||
raise ValueError(
|
||||
f"Max ticks must be less than {m5.MaxTick}, not {max_tick}"
|
||||
)
|
||||
self._max_ticks = max_tick
|
||||
|
||||
def get_max_ticks(self) -> int:
|
||||
assert hasattr(self, "_max_ticks"), "Max ticks not set"
|
||||
return self._max_ticks
|
||||
|
||||
def schedule_simpoint(self, simpoint_start_insts: List[int]) -> None:
|
||||
"""
|
||||
Schedule ``SIMPOINT_BEGIN`` exit events
|
||||
@@ -512,6 +596,38 @@ class Simulator:
|
||||
|
||||
return to_return
|
||||
|
||||
def override_outdir(self, new_outdir: Path) -> None:
|
||||
"""This function can be used to override the output directory locatiomn
|
||||
Assiming the path passed is valid, the directory will be created
|
||||
and set as the new output directory, thus overriding what was set at
|
||||
the gem5 command line. Is there fore advised this function is used with
|
||||
caution. Its primary use is for swaning multiple gem5 processes from
|
||||
a gem5 process to allow the child processes their own output directory.
|
||||
|
||||
:param new_outdir: The new output directory to be used instead of that
|
||||
set at the gem5 command line.
|
||||
"""
|
||||
|
||||
if self._instantiated:
|
||||
raise Exception(
|
||||
"Cannot override the output directory after the simulation "
|
||||
"has been instantiated."
|
||||
)
|
||||
from m5 import options
|
||||
|
||||
from _m5.core import setOutputDir
|
||||
|
||||
new_outdir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if not new_outdir.exists():
|
||||
raise Exception(f"Directory '{new_outdir}' does not exist")
|
||||
|
||||
if not new_outdir.is_dir():
|
||||
raise Exception(f"'{new_outdir}' is not a directory")
|
||||
|
||||
options.outdir = str(new_outdir)
|
||||
setOutputDir(options.outdir)
|
||||
|
||||
def _instantiate(self) -> None:
|
||||
"""
|
||||
This method will instantiate the board and carry out necessary
|
||||
@@ -573,7 +689,7 @@ class Simulator:
|
||||
# any final things.
|
||||
self._board._post_instantiate()
|
||||
|
||||
def run(self, max_ticks: int = m5.MaxTick) -> None:
|
||||
def run(self, max_ticks: Optional[int] = None) -> None:
|
||||
"""
|
||||
This function will start or continue the simulator run and handle exit
|
||||
events accordingly.
|
||||
@@ -582,9 +698,17 @@ class Simulator:
|
||||
run. If this ``max_ticks`` value is met, a ``MAX_TICK``
|
||||
exit event is received, if another simulation exit
|
||||
event is met the tick count is reset. This is the
|
||||
**maximum number of ticks per simulation run**.
|
||||
**maximum number of ticks per simulation run.
|
||||
"""
|
||||
|
||||
if max_ticks and max_ticks != self._max_ticks:
|
||||
warn(
|
||||
"Max ticks has already been set prior to setting it through "
|
||||
"the run call. In these cases the max ticks set through the "
|
||||
"`run` function is used"
|
||||
)
|
||||
self.set_max_ticks(max_ticks)
|
||||
|
||||
# Check to ensure no banned module has been imported.
|
||||
for banned_module in self._banned_modules.keys():
|
||||
if banned_module in sys.modules:
|
||||
@@ -599,7 +723,7 @@ class Simulator:
|
||||
|
||||
# This while loop will continue until an a generator yields True.
|
||||
while True:
|
||||
self._last_exit_event = m5.simulate(max_ticks)
|
||||
self._last_exit_event = m5.simulate(self.get_max_ticks())
|
||||
|
||||
# Translate the exit event cause to the exit event enum.
|
||||
exit_enum = ExitEvent.translate_exit_status(
|
||||
|
||||
@@ -10,10 +10,36 @@ The goal of this code is to enable users to use a *single* set of python scripts
|
||||
We must reimplement some of the multiprocessing module because it is not flexible enough to allow for customized command line parameter to the "python" executable (gem5 in our case).
|
||||
To get around this, I extended the Process and context objects to be gem5 specific.
|
||||
|
||||
The next steps is to wrap the Process and Pool types with gem5-specific versions that will improve their usability for our needs.
|
||||
With this changeset, these objects are usable, but it will require significant user effort to reach the goal of running/analyzing many different gem5 simulations.
|
||||
One specific issue is that the user has to call `m5.setOutputDir` or `Simulator.override_outdir` or else all of the output will overwrite each other.
|
||||
We *strongly* recommend that the user use the `multisim` module to run multiple simulations in parallel.
|
||||
|
||||
## Example use
|
||||
## Multisim
|
||||
|
||||
The `multisim` module is a higher-level abstraction that uses the `multiprocessing` module to run multiple simulations in parallel.
|
||||
|
||||
You can declare a set of `Simulator` objects (via `add_simulator`) and then run them all in parallel from the command line.
|
||||
|
||||
To run all the simulations defined in a script:
|
||||
|
||||
```shell
|
||||
<gem5-binary> -m gem5.utils.multisim <path-to-script>
|
||||
```
|
||||
|
||||
To run a specific simulation defined in this script:
|
||||
|
||||
```shell
|
||||
<gem5-binary> <path-to-script> \
|
||||
process_id_1
|
||||
```
|
||||
|
||||
To list all the IDs of the simulations defined in this script:
|
||||
|
||||
```shell
|
||||
<gem5-binary> <path-to-script> -l
|
||||
```
|
||||
|
||||
## Example use of raw multiprocessing
|
||||
|
||||
test.py:
|
||||
|
||||
|
||||
@@ -68,9 +68,12 @@ def _gem5_args_for_multiprocessing(name):
|
||||
# --dot-config, --dot-dvfs-config, --debug-file, --remote-gdb-port, -c
|
||||
|
||||
arguments = [
|
||||
f"--outdir={options.outdir}/{name}",
|
||||
f"--stdout-file={options.stdout_file}",
|
||||
f"--stderr-file={options.stderr_file}",
|
||||
# Keep the original outdir. This will be overridden by multisim
|
||||
f"--outdir={options.outdir}",
|
||||
# Update the stdout and stderr names so we can see them.
|
||||
f"--stdout-file={name}_{options.stdout_file}",
|
||||
f"--stderr-file={name}_{options.stderr_file}",
|
||||
# Keep the stats file name. It will be in the new outdir
|
||||
f"--stats-file={options.stats_file}",
|
||||
]
|
||||
if options.redirect_stdout:
|
||||
|
||||
33
src/python/gem5/utils/multisim/__init__.py
Normal file
33
src/python/gem5/utils/multisim/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2024 The Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from .multisim import (
|
||||
add_simulator,
|
||||
get_simulator_ids,
|
||||
num_simulators,
|
||||
run,
|
||||
set_num_processes,
|
||||
)
|
||||
60
src/python/gem5/utils/multisim/__main__.py
Normal file
60
src/python/gem5/utils/multisim/__main__.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# Copyright (c) 2024 The Regents of the University of California
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
This module is the entry point for the multi-simulation (MultiSim) framework.
|
||||
It provides a CLI using argparse to obtain the path to the simulation
|
||||
configuration script and the number of processes to run in parallel.
|
||||
"""
|
||||
from gem5.utils.multisim.multisim import (
|
||||
module_run,
|
||||
run,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
global module_run
|
||||
module_run = True
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Pass the config script specifying the simulations to run "
|
||||
"using multisim."
|
||||
)
|
||||
parser.add_argument(
|
||||
"config",
|
||||
type=str,
|
||||
help="The path to the config script specifying the simulations to run using multisim.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
run(module_path=Path(args.config))
|
||||
|
||||
|
||||
if __name__ == "__m5_main__":
|
||||
main()
|
||||
300
src/python/gem5/utils/multisim/multisim.py
Normal file
300
src/python/gem5/utils/multisim/multisim.py
Normal file
@@ -0,0 +1,300 @@
|
||||
# Copyright (c) 2024 The Regents of the University of California
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met: redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer;
|
||||
# redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution;
|
||||
# neither the name of the copyright holders nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""This module contains the gem5 MultiSim framework. The gem5 MultiSim
|
||||
work functions by allowing a user to specify multiple simulations to run from
|
||||
a single gem5 config script. The MuliSim framework will then run these
|
||||
simulations in parallel using the Python multiprocessing library.
|
||||
|
||||
The framework works by having the user add the simulators in a configuration
|
||||
via the `add_simulator` function, each with a unique, user specified, id. This
|
||||
adds the different simulations to run in parallel to a global list. The
|
||||
MultiSim framework then uses the Python multiprocessing library to run the
|
||||
simulations in parallel by loading the config script as a module in each child
|
||||
process and then selecting the simulation to run via the iid in the global set
|
||||
of scheduled simulators jobs to run.
|
||||
The only difference between the child processes is the id of the simulator.
|
||||
|
||||
Important notes
|
||||
---------------
|
||||
|
||||
1. You cannot load/instantiate the simulators in the main process. You cannot
|
||||
even load the config script (i.e., `import config_script`). This means the
|
||||
config script is passed as a string referencing the config script as a module.
|
||||
This script is then passed to the child processes to load.
|
||||
|
||||
2. The config script cannot accept parameters. It must be parameterless.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import multiprocessing
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Optional,
|
||||
Set,
|
||||
)
|
||||
|
||||
# A global variable which __main__.py flips to `True` when multisim is run as
|
||||
# an executable module.
|
||||
module_run = False
|
||||
|
||||
# A global variable to store the simulators to run in parallel. If `None`, then
|
||||
# `None` is passed to the `multiprocessing.Pool` which instructs
|
||||
# multiprocessing to use the maximum number of available threads.
|
||||
# threads.
|
||||
_num_processes = None
|
||||
|
||||
_multi_sim: Set["Simulator"] = set()
|
||||
|
||||
|
||||
def _load_module(module_path: Path) -> None:
|
||||
"""Load the module at the given path."""
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"gem5target", str(module_path)
|
||||
)
|
||||
modulevar = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(modulevar)
|
||||
|
||||
|
||||
def _get_simulator_ids_child_process(id_list, module_path: Path) -> None:
|
||||
"""Get the ids of the simulations to be run.
|
||||
|
||||
This function is passed to the Python multiprocessing module and run with
|
||||
the correct module path in the `_get_simulator_ids` function. This function
|
||||
is run in a child process which loads the module (config script) then reads
|
||||
the IDs.
|
||||
|
||||
Note: We run this as child process as we cannot load the config script as
|
||||
a module in the main process. This function is used in
|
||||
`get_simulator_ids` and should be used separately.
|
||||
"""
|
||||
|
||||
_load_module(module_path)
|
||||
global _multi_sim
|
||||
if len(id_list) != 0:
|
||||
id_list *= 0
|
||||
id_list.extend([sim.get_id() for sim in _multi_sim])
|
||||
|
||||
|
||||
def _get_num_processes_child_process(
|
||||
num_processes_dict, module_path: Path
|
||||
) -> None:
|
||||
"""Get the ids of the simulations to be run.
|
||||
|
||||
This function is passed to the Python multiprocessing module and run with
|
||||
the correct module path in the `_get_simulator_ids` function. This function
|
||||
is run in a child process which loads the module (config script) then reads
|
||||
the IDs.
|
||||
|
||||
Note: We run this as child process as we cannot load the config script as
|
||||
a module in the main process. This function is used in
|
||||
`get_simulator_ids` and should be used separately.
|
||||
"""
|
||||
|
||||
_load_module(module_path)
|
||||
global _num_processes
|
||||
num_processes_dict["num_processes"] = _num_processes
|
||||
|
||||
|
||||
def get_simulator_ids(config_module_path: Path) -> list[str]:
|
||||
"""This is a hack to determine the IDs of the simulations we are to run.
|
||||
The only way we can know is by importing the module, which we can only do
|
||||
in the child processes. We therefore create a child process with the
|
||||
sole purpose of importing the module and returning the IDs via
|
||||
a `multiprocessing.Manager` dictionary.
|
||||
|
||||
This function handles the creation of the `multiprocessing.Manager` and the
|
||||
`multiprocessing.Process`. It then waits for the process to finish to then
|
||||
return the ids as a set of strings.
|
||||
"""
|
||||
|
||||
manager = multiprocessing.Manager()
|
||||
id_list = manager.list()
|
||||
p = multiprocessing.Process(
|
||||
target=_get_simulator_ids_child_process,
|
||||
args=(id_list, config_module_path),
|
||||
)
|
||||
p.start()
|
||||
p.join()
|
||||
return id_list
|
||||
|
||||
|
||||
def get_num_processes(config_module_path: Path) -> Optional[int]:
|
||||
manager = multiprocessing.Manager()
|
||||
num_processes_dict = manager.dict()
|
||||
p = multiprocessing.Process(
|
||||
target=_get_num_processes_child_process,
|
||||
args=(num_processes_dict, config_module_path),
|
||||
)
|
||||
p.start()
|
||||
p.join()
|
||||
return num_processes_dict["num_processes"]
|
||||
|
||||
|
||||
def _run(module_path: Path, id: str) -> None:
|
||||
"""Run the simulator with the ID specified."""
|
||||
|
||||
_load_module(module_path)
|
||||
|
||||
global _multi_sim
|
||||
sim_list = [sim for sim in _multi_sim if sim.get_id() == id]
|
||||
|
||||
assert len(sim_list) != 0, f"No simulator with id '{id}' found."
|
||||
assert len(sim_list) == 1, f"Multiple simulators with id '{id}' found."
|
||||
import m5
|
||||
|
||||
subdir = Path(Path(m5.options.outdir) / Path(sim_list[0].get_id()))
|
||||
sim_list[0].override_outdir(subdir)
|
||||
|
||||
sim_list[0].run()
|
||||
|
||||
|
||||
def run(module_path: Path, processes: Optional[int] = None) -> None:
|
||||
"""Run the simulators specified in the module in parallel.
|
||||
|
||||
:param module_path: The path to the module containing the simulators to
|
||||
run.
|
||||
:param processes: The number of processes to run in parallel. If not
|
||||
specified, the number of available threads will be used.
|
||||
"""
|
||||
|
||||
assert len(_multi_sim) == 0, (
|
||||
"Simulators instantiated in main thread instead of child thread "
|
||||
"(prior to determining number of jobs)."
|
||||
)
|
||||
|
||||
# Get the simulator IDs. This both provides us a list of targets
|
||||
# and, by-proxy, the number of jobs.
|
||||
ids = get_simulator_ids(module_path)
|
||||
max_num_processes = get_num_processes(module_path)
|
||||
|
||||
assert len(_multi_sim) == 0, (
|
||||
"Simulators instantiated in main thread instead of child thread "
|
||||
"(after determining number of jobs)."
|
||||
)
|
||||
|
||||
# Setup the multiprocessing pool. If the number of processes is not
|
||||
# specified (i.e. `None`) the default is the number or available threads.
|
||||
from ..multiprocessing.context import gem5Context
|
||||
|
||||
pool = gem5Context().Pool(processes=max_num_processes, maxtasksperchild=1)
|
||||
|
||||
# Use the starmap function to create N child processes each with same
|
||||
# module path (the config script specifying all simulations using MultiSim)
|
||||
# but a different ID. The ID is used to select the correct simulator to
|
||||
# run.
|
||||
pool.starmap(_run, zip([module_path for _ in range(len(ids))], tuple(ids)))
|
||||
|
||||
|
||||
def set_num_processes(num_processes: int) -> None:
|
||||
"""Set the max number of processes to run in parallel.
|
||||
|
||||
:param num_processes: The number of processes to run in parallel.
|
||||
"""
|
||||
if num_processes < 1:
|
||||
raise ValueError("Number of processes must be greater than 0.")
|
||||
if isinstance(num_processes, int):
|
||||
global _num_processes
|
||||
_num_processes = num_processes
|
||||
else:
|
||||
raise ValueError("Number of processes must be an integer.")
|
||||
|
||||
|
||||
def num_simulators() -> int:
|
||||
"""Returns the number of simulators added to the MultiSim."""
|
||||
return len(_multi_sim)
|
||||
|
||||
|
||||
def add_simulator(simulator: "Simulator") -> None:
|
||||
"""Add a single simulator to the Multisim. Doing so informs the simulators
|
||||
to run this simulator via multiprocessing.
|
||||
|
||||
**Note:** If this function is not run using the MultiSim module then the
|
||||
user will be prompted to either do so if they desire multiple gem5
|
||||
processes or to pass the id of the simulator to run. This function will
|
||||
attempt to run the simulation with the id passed as an argument. If
|
||||
such simulation exists the simulation will end without failure (or any
|
||||
simulations having been run).
|
||||
|
||||
:param simulator: The simulator to add to the multisim.
|
||||
:param id: The id of the simulator. This is used to reference the
|
||||
simulation. This is particularly important when referencing the correct
|
||||
m5out subdirectory.
|
||||
"""
|
||||
|
||||
global _multi_sim
|
||||
if not simulator.get_id():
|
||||
# The default simulator id is the length of the current set of
|
||||
# simulators. This is used to ensure that the simulator has a unique
|
||||
# id.
|
||||
simulator.set_id(f"sim_{len(_multi_sim)}")
|
||||
_multi_sim.add(simulator)
|
||||
|
||||
# The following code is used to enable a user to run a single simulation
|
||||
# from the config script, based on an ID, in the case the config script is
|
||||
# passed a traditional gem5 config and not via the multisim module.
|
||||
global module_run
|
||||
if not module_run:
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run a specific simulation based on the id."
|
||||
)
|
||||
parser.add_argument(
|
||||
"id",
|
||||
type=str,
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="The id of the simulator to run. "
|
||||
"WARNING: If the id is invalid nothing is done.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--list",
|
||||
help="List the ids of the simulators to run.",
|
||||
action="store_true",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.list:
|
||||
print(simulator.get_id())
|
||||
elif not args.id:
|
||||
raise Exception(
|
||||
"If running this script directly as a configuration script "
|
||||
"then a single argument must be specified: the id of the "
|
||||
"simulator to run. This will run the simulation associated "
|
||||
"with that id and no other. If the intent is instead to run "
|
||||
"the script via the MultiSim utility then run this script via "
|
||||
"the multisim module: "
|
||||
"`<gem5> -m gem5.utils.multisim <config_script>`.\n\n"
|
||||
"To list the ids of the simulators to run use the `--list` "
|
||||
"(`-l`) flag."
|
||||
)
|
||||
elif args.id == simulator.get_id():
|
||||
import m5
|
||||
|
||||
subdir = Path(Path(m5.options.outdir) / Path(simulator.get_id()))
|
||||
simulator.override_outdir(subdir)
|
||||
simulator.run()
|
||||
@@ -222,9 +222,9 @@ board.set_kernel_disk_workload(
|
||||
simulator = Simulator(board=board)
|
||||
|
||||
if args.tick_exit:
|
||||
simulator.run(max_ticks=args.tick_exit)
|
||||
else:
|
||||
simulator.run()
|
||||
simulator.set_max_ticks(args.tick_exit)
|
||||
|
||||
simulator.run()
|
||||
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
|
||||
@@ -70,9 +70,9 @@ board.set_se_binary_workload(
|
||||
)
|
||||
)
|
||||
|
||||
sim = Simulator(board=board, full_system=False)
|
||||
max_ticks = 10**6
|
||||
sim.run(max_ticks=max_ticks)
|
||||
sim = Simulator(board=board, full_system=False, max_ticks=10**6)
|
||||
|
||||
sim.run()
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
sim.get_current_tick(), sim.get_last_exit_event_cause()
|
||||
|
||||
@@ -73,9 +73,9 @@ board.set_se_binary_workload(
|
||||
obtain_resource("power-hello", resource_version="1.0.0")
|
||||
)
|
||||
|
||||
sim = Simulator(board=board, full_system=False)
|
||||
max_ticks = 10**6
|
||||
sim.run(max_ticks=max_ticks)
|
||||
sim = Simulator(board=board, full_system=False, max_ticks=10**6)
|
||||
|
||||
sim.run()
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
sim.get_current_tick(), sim.get_last_exit_event_cause()
|
||||
|
||||
@@ -76,9 +76,8 @@ board.set_se_binary_workload(
|
||||
)
|
||||
)
|
||||
|
||||
sim = Simulator(board=board, full_system=False)
|
||||
max_ticks = 10**6
|
||||
sim.run(max_ticks=max_ticks)
|
||||
sim = Simulator(board=board, full_system=False, max_ticks=10**6)
|
||||
sim.run()
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
sim.get_current_tick(), sim.get_last_exit_event_cause()
|
||||
|
||||
@@ -85,10 +85,10 @@ board.set_kernel_disk_workload(
|
||||
),
|
||||
)
|
||||
|
||||
sim = Simulator(board=board, full_system=True)
|
||||
sim = Simulator(board=board, full_system=True, max_ticks=10**10)
|
||||
print("Beginning simulation!")
|
||||
|
||||
sim.run(max_ticks=10**10)
|
||||
sim.run()
|
||||
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
|
||||
@@ -87,11 +87,11 @@ board.set_kernel_disk_workload(
|
||||
disk_image=obtain_resource("x86-ubuntu-18.04-img"),
|
||||
)
|
||||
|
||||
sim = Simulator(board=board, full_system=True)
|
||||
sim = Simulator(board=board, full_system=True, max_ticks=10**6)
|
||||
print("Beginning simulation!")
|
||||
|
||||
max_ticks = 10**6
|
||||
sim.run(max_ticks=max_ticks)
|
||||
|
||||
sim.run()
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
sim.get_current_tick(), sim.get_last_exit_event_cause()
|
||||
|
||||
@@ -76,9 +76,9 @@ board.set_se_binary_workload(
|
||||
)
|
||||
)
|
||||
|
||||
sim = Simulator(board=board, full_system=False)
|
||||
max_ticks = 10**6
|
||||
sim.run(max_ticks=max_ticks)
|
||||
sim = Simulator(board=board, full_system=False, max_ticks=10**6)
|
||||
|
||||
sim.run()
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
sim.get_current_tick(), sim.get_last_exit_event_cause()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
process_0
|
||||
process_1
|
||||
process_2
|
||||
process_3
|
||||
process_4
|
||||
@@ -348,6 +348,86 @@ gem5_verify_config(
|
||||
length=constants.long_tag,
|
||||
)
|
||||
|
||||
gem5_verify_config(
|
||||
name="test-gem5-library-example-multisim-fs-x86-npb",
|
||||
fixtures=(),
|
||||
verifiers=(),
|
||||
config=joinpath(
|
||||
config.base_dir,
|
||||
"configs",
|
||||
"example",
|
||||
"gem5_library",
|
||||
"multisim",
|
||||
"multisim-fs-x86-npb.py",
|
||||
),
|
||||
config_args=[],
|
||||
gem5_args=["-m", "gem5.utils.multisim"],
|
||||
valid_isas=(constants.all_compiled_tag,),
|
||||
valid_hosts=constants.supported_hosts,
|
||||
length=constants.long_tag,
|
||||
)
|
||||
|
||||
gem5_verify_config(
|
||||
name="test-gem5-library-example-multisim-print-this",
|
||||
fixtures=(),
|
||||
verifiers=(),
|
||||
config=joinpath(
|
||||
config.base_dir,
|
||||
"configs",
|
||||
"example",
|
||||
"gem5_library",
|
||||
"multisim",
|
||||
"multisim-print-this.py",
|
||||
),
|
||||
config_args=[],
|
||||
gem5_args=["-m", "gem5.utils.multisim"],
|
||||
valid_isas=(constants.all_compiled_tag,),
|
||||
valid_hosts=constants.supported_hosts,
|
||||
length=constants.quick_tag,
|
||||
)
|
||||
|
||||
gem5_verify_config(
|
||||
name="test-gem5-library-example-multisim-print-this-list",
|
||||
fixtures=(),
|
||||
verifiers=(
|
||||
verifier.MatchStdoutNoPerf(
|
||||
joinpath(getcwd(), "ref/simout_multisim_print_this_list.txt")
|
||||
),
|
||||
),
|
||||
config=joinpath(
|
||||
config.base_dir,
|
||||
"configs",
|
||||
"example",
|
||||
"gem5_library",
|
||||
"multisim",
|
||||
"multisim-print-this.py",
|
||||
),
|
||||
config_args=["--list"],
|
||||
gem5_args=[],
|
||||
valid_isas=(constants.all_compiled_tag,),
|
||||
valid_hosts=constants.supported_hosts,
|
||||
length=constants.quick_tag,
|
||||
)
|
||||
|
||||
gem5_verify_config(
|
||||
name="test-gem5-library-example-multisim-print-this-single-process",
|
||||
fixtures=(),
|
||||
verifiers=(),
|
||||
config=joinpath(
|
||||
config.base_dir,
|
||||
"configs",
|
||||
"example",
|
||||
"gem5_library",
|
||||
"multisim",
|
||||
"multisim-print-this.py",
|
||||
),
|
||||
config_args=["process_1"],
|
||||
gem5_args=[],
|
||||
valid_isas=(constants.all_compiled_tag,),
|
||||
valid_hosts=constants.supported_hosts,
|
||||
length=constants.quick_tag,
|
||||
)
|
||||
|
||||
# The LoopPoint-Checkpointing feature is still under development, therefore
|
||||
# these tests are temporarily disabled until this feature is complete.#
|
||||
|
||||
|
||||
@@ -173,9 +173,9 @@ board.set_workload(workload)
|
||||
simulator = Simulator(board=board)
|
||||
|
||||
if args.tick_exit:
|
||||
simulator.run(max_ticks=args.tick_exit)
|
||||
else:
|
||||
simulator.run()
|
||||
simulator.set_max_ticks(args.tick_exit)
|
||||
|
||||
simulator.run()
|
||||
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
|
||||
@@ -145,9 +145,9 @@ board.set_workload(list(suite)[0])
|
||||
simulator = Simulator(board=board, full_system=args.fs_sim)
|
||||
|
||||
if args.tick_exit:
|
||||
simulator.run(max_ticks=args.tick_exit)
|
||||
else:
|
||||
simulator.run()
|
||||
simulator.set_max_ticks(args.tick_exit)
|
||||
|
||||
simulator.run()
|
||||
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
|
||||
@@ -112,9 +112,9 @@ if args.set_ticks_before:
|
||||
simulator = Simulator(board=motherboard)
|
||||
|
||||
if args.set_ticks_at_execution:
|
||||
simulator.run(max_ticks=args.set_ticks_at_execution)
|
||||
else:
|
||||
simulator.run()
|
||||
simulator.set_max_ticks(args.set_ticks_at_execution)
|
||||
|
||||
simulator.run()
|
||||
|
||||
# Set the max ticks after the simulator run.
|
||||
if args.set_ticks_after:
|
||||
|
||||
@@ -201,9 +201,9 @@ print("Beginning simulation!")
|
||||
simulator = Simulator(board=motherboard)
|
||||
|
||||
if args.tick_exit:
|
||||
simulator.run(max_ticks=args.tick_exit)
|
||||
else:
|
||||
simulator.run()
|
||||
simulator.set_max_tick(args.tick_exit)
|
||||
|
||||
simulator.run()
|
||||
|
||||
print(
|
||||
"Exiting @ tick {} because {}.".format(
|
||||
|
||||
Reference in New Issue
Block a user