diff --git a/configs/example/gem5_library/checkpoints/riscv-hello-save-checkpoint.py b/configs/example/gem5_library/checkpoints/riscv-hello-save-checkpoint.py index b024a3a44a..c231203d56 100644 --- a/configs/example/gem5_library/checkpoints/riscv-hello-save-checkpoint.py +++ b/configs/example/gem5_library/checkpoints/riscv-hello-save-checkpoint.py @@ -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( diff --git a/configs/example/gem5_library/multisim/multisim-fs-x86-npb.py b/configs/example/gem5_library/multisim/multisim-fs-x86-npb.py new file mode 100644 index 0000000000..eff2b0c48f --- /dev/null +++ b/configs/example/gem5_library/multisim/multisim-fs-x86-npb.py @@ -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 + -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 + configs/example/gem5_library/multisim/multisim-fs-x86-npb.py \ + # e.g. npb-bt-a_cores-1 +``` + +3. To list all the IDs of the simulations defined in this script: + +```shell + 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) diff --git a/configs/example/gem5_library/multisim/multisim-print-this.py b/configs/example/gem5_library/multisim/multisim-print-this.py new file mode 100644 index 0000000000..bd724a5d92 --- /dev/null +++ b/configs/example/gem5_library/multisim/multisim-print-this.py @@ -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 + -m gem5.utils.multisim \ + configs/example/gem5_library/multisim/multisim-print-this.py +``` + +2. To run a specific simulation defined in this script: + +```shell + 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 + 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}")) diff --git a/src/python/SConscript b/src/python/SConscript index af117e4a14..3aed9f03e3 100644 --- a/src/python/SConscript +++ b/src/python/SConscript @@ -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', diff --git a/src/python/gem5/components/boards/kernel_disk_workload.py b/src/python/gem5/components/boards/kernel_disk_workload.py index dcf20081fa..283b9f31f6 100644 --- a/src/python/gem5/components/boards/kernel_disk_workload.py +++ b/src/python/gem5/components/boards/kernel_disk_workload.py @@ -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: diff --git a/src/python/gem5/components/cachehierarchies/classic/no_cache.py b/src/python/gem5/components/cachehierarchies/classic/no_cache.py index 3fb37bb43c..e6ec89b660 100644 --- a/src/python/gem5/components/cachehierarchies/classic/no_cache.py +++ b/src/python/gem5/components/cachehierarchies/classic/no_cache.py @@ -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: diff --git a/src/python/gem5/components/cachehierarchies/classic/private_l1_cache_hierarchy.py b/src/python/gem5/components/cachehierarchies/classic/private_l1_cache_hierarchy.py index 97348fdbe5..25063fc113 100644 --- a/src/python/gem5/components/cachehierarchies/classic/private_l1_cache_hierarchy.py +++ b/src/python/gem5/components/cachehierarchies/classic/private_l1_cache_hierarchy.py @@ -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 diff --git a/src/python/gem5/components/cachehierarchies/classic/private_l1_private_l2_cache_hierarchy.py b/src/python/gem5/components/cachehierarchies/classic/private_l1_private_l2_cache_hierarchy.py index 78981a6f2b..3996418a8c 100644 --- a/src/python/gem5/components/cachehierarchies/classic/private_l1_private_l2_cache_hierarchy.py +++ b/src/python/gem5/components/cachehierarchies/classic/private_l1_private_l2_cache_hierarchy.py @@ -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: diff --git a/src/python/gem5/components/cachehierarchies/classic/private_l1_shared_l2_cache_hierarchy.py b/src/python/gem5/components/cachehierarchies/classic/private_l1_shared_l2_cache_hierarchy.py index 4e2c622d62..133da14755 100644 --- a/src/python/gem5/components/cachehierarchies/classic/private_l1_shared_l2_cache_hierarchy.py +++ b/src/python/gem5/components/cachehierarchies/classic/private_l1_shared_l2_cache_hierarchy.py @@ -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: diff --git a/src/python/gem5/simulate/simulator.py b/src/python/gem5/simulate/simulator.py index 66f67d6ffb..3ca4f107b8 100644 --- a/src/python/gem5/simulate/simulator.py +++ b/src/python/gem5/simulate/simulator.py @@ -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( diff --git a/src/python/gem5/utils/multiprocessing/README.md b/src/python/gem5/utils/multiprocessing/README.md index c6b0406e54..51c3d6edc0 100644 --- a/src/python/gem5/utils/multiprocessing/README.md +++ b/src/python/gem5/utils/multiprocessing/README.md @@ -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 + -m gem5.utils.multisim +``` + +To run a specific simulation defined in this script: + +```shell + \ + process_id_1 +``` + +To list all the IDs of the simulations defined in this script: + +```shell + -l +``` + +## Example use of raw multiprocessing test.py: diff --git a/src/python/gem5/utils/multiprocessing/_command_line.py b/src/python/gem5/utils/multiprocessing/_command_line.py index 9922447295..e26428d8f3 100644 --- a/src/python/gem5/utils/multiprocessing/_command_line.py +++ b/src/python/gem5/utils/multiprocessing/_command_line.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: diff --git a/src/python/gem5/utils/multisim/__init__.py b/src/python/gem5/utils/multisim/__init__.py new file mode 100644 index 0000000000..f9ba9fd59d --- /dev/null +++ b/src/python/gem5/utils/multisim/__init__.py @@ -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, +) diff --git a/src/python/gem5/utils/multisim/__main__.py b/src/python/gem5/utils/multisim/__main__.py new file mode 100644 index 0000000000..db78172aea --- /dev/null +++ b/src/python/gem5/utils/multisim/__main__.py @@ -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() diff --git a/src/python/gem5/utils/multisim/multisim.py b/src/python/gem5/utils/multisim/multisim.py new file mode 100644 index 0000000000..eb23f618e4 --- /dev/null +++ b/src/python/gem5/utils/multisim/multisim.py @@ -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: " + "` -m gem5.utils.multisim `.\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() diff --git a/tests/gem5/arm_boot_tests/configs/arm_boot_exit_run.py b/tests/gem5/arm_boot_tests/configs/arm_boot_exit_run.py index e2d496352a..8690d35e3b 100644 --- a/tests/gem5/arm_boot_tests/configs/arm_boot_exit_run.py +++ b/tests/gem5/arm_boot_tests/configs/arm_boot_exit_run.py @@ -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( diff --git a/tests/gem5/checkpoint_tests/configs/arm-hello-save-checkpoint.py b/tests/gem5/checkpoint_tests/configs/arm-hello-save-checkpoint.py index 18055d3f32..fac8083113 100644 --- a/tests/gem5/checkpoint_tests/configs/arm-hello-save-checkpoint.py +++ b/tests/gem5/checkpoint_tests/configs/arm-hello-save-checkpoint.py @@ -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() diff --git a/tests/gem5/checkpoint_tests/configs/power-hello-save-checkpoint.py b/tests/gem5/checkpoint_tests/configs/power-hello-save-checkpoint.py index 18b5793857..5e86bd183f 100644 --- a/tests/gem5/checkpoint_tests/configs/power-hello-save-checkpoint.py +++ b/tests/gem5/checkpoint_tests/configs/power-hello-save-checkpoint.py @@ -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() diff --git a/tests/gem5/checkpoint_tests/configs/sparc-hello-save-checkpoint.py b/tests/gem5/checkpoint_tests/configs/sparc-hello-save-checkpoint.py index b7a60e39b3..2a0cf29799 100644 --- a/tests/gem5/checkpoint_tests/configs/sparc-hello-save-checkpoint.py +++ b/tests/gem5/checkpoint_tests/configs/sparc-hello-save-checkpoint.py @@ -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() diff --git a/tests/gem5/checkpoint_tests/configs/x86-fs-restore-checkpoint.py b/tests/gem5/checkpoint_tests/configs/x86-fs-restore-checkpoint.py index 574ccd8710..799cfea651 100644 --- a/tests/gem5/checkpoint_tests/configs/x86-fs-restore-checkpoint.py +++ b/tests/gem5/checkpoint_tests/configs/x86-fs-restore-checkpoint.py @@ -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( diff --git a/tests/gem5/checkpoint_tests/configs/x86-fs-save-checkpoint.py b/tests/gem5/checkpoint_tests/configs/x86-fs-save-checkpoint.py index e206cb39b8..4bb07941b8 100644 --- a/tests/gem5/checkpoint_tests/configs/x86-fs-save-checkpoint.py +++ b/tests/gem5/checkpoint_tests/configs/x86-fs-save-checkpoint.py @@ -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() diff --git a/tests/gem5/checkpoint_tests/configs/x86-hello-save-checkpoint.py b/tests/gem5/checkpoint_tests/configs/x86-hello-save-checkpoint.py index 111ddafbe5..fd6126ada7 100644 --- a/tests/gem5/checkpoint_tests/configs/x86-hello-save-checkpoint.py +++ b/tests/gem5/checkpoint_tests/configs/x86-hello-save-checkpoint.py @@ -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() diff --git a/tests/gem5/gem5_library_example_tests/ref/simout_multisim_print_this_list.txt b/tests/gem5/gem5_library_example_tests/ref/simout_multisim_print_this_list.txt new file mode 100644 index 0000000000..1934e3b536 --- /dev/null +++ b/tests/gem5/gem5_library_example_tests/ref/simout_multisim_print_this_list.txt @@ -0,0 +1,5 @@ +process_0 +process_1 +process_2 +process_3 +process_4 diff --git a/tests/gem5/gem5_library_example_tests/test_gem5_library_examples.py b/tests/gem5/gem5_library_example_tests/test_gem5_library_examples.py index 9c88ff4307..f91b4bba15 100644 --- a/tests/gem5/gem5_library_example_tests/test_gem5_library_examples.py +++ b/tests/gem5/gem5_library_example_tests/test_gem5_library_examples.py @@ -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.# diff --git a/tests/gem5/riscv_boot_tests/configs/riscv_boot_exit_run.py b/tests/gem5/riscv_boot_tests/configs/riscv_boot_exit_run.py index d04d5aa5f0..06a9d7e4be 100644 --- a/tests/gem5/riscv_boot_tests/configs/riscv_boot_exit_run.py +++ b/tests/gem5/riscv_boot_tests/configs/riscv_boot_exit_run.py @@ -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( diff --git a/tests/gem5/suite_tests/configs/suite_run_workload.py b/tests/gem5/suite_tests/configs/suite_run_workload.py index ebf86b5eb0..9b8629fa32 100644 --- a/tests/gem5/suite_tests/configs/suite_run_workload.py +++ b/tests/gem5/suite_tests/configs/suite_run_workload.py @@ -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( diff --git a/tests/gem5/to_tick/configs/tick-to-max.py b/tests/gem5/to_tick/configs/tick-to-max.py index ea99d90eda..9d7727a39b 100644 --- a/tests/gem5/to_tick/configs/tick-to-max.py +++ b/tests/gem5/to_tick/configs/tick-to-max.py @@ -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: diff --git a/tests/gem5/x86_boot_tests/configs/x86_boot_exit_run.py b/tests/gem5/x86_boot_tests/configs/x86_boot_exit_run.py index 7ad8682754..62bcaf8745 100644 --- a/tests/gem5/x86_boot_tests/configs/x86_boot_exit_run.py +++ b/tests/gem5/x86_boot_tests/configs/x86_boot_exit_run.py @@ -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(