| /* | |
| * Copyright (c) 2021 Arm Limited | |
| * All rights reserved | |
| * | |
| * The license below extends only to copyright in the software and shall | |
| * not be construed as granting a license to any other intellectual | |
| * property including but not limited to intellectual property relating | |
| * to a hardware implementation of the functionality of the software | |
| * licensed hereunder. You may use the software subject to the license | |
| * terms below provided that you ensure that this notice is replicated | |
| * unmodified and in its entirety in all distributions of the software, | |
| * modified or unmodified, in source code or in binary form. | |
| * | |
| * Copyright (c) 2006 The Regents of The University of Michigan | |
| * Copyright (c) 2013 Advanced Micro Devices, Inc. | |
| * Copyright (c) 2013 Mark D. Hill and David A. Wood | |
| * 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. | |
| */ | |
| #include "sim/simulate.hh" | |
| #include <atomic> | |
| #include <thread> | |
| #include "base/logging.hh" | |
| #include "base/pollevent.hh" | |
| #include "base/types.hh" | |
| #include "sim/async.hh" | |
| #include "sim/eventq.hh" | |
| #include "sim/sim_events.hh" | |
| #include "sim/sim_exit.hh" | |
| #include "sim/stat_control.hh" | |
| namespace gem5 | |
| { | |
| //! forward declaration | |
| Event *doSimLoop(EventQueue *); | |
| GlobalSimLoopExitEvent *simulate_limit_event = nullptr; | |
| class SimulatorThreads | |
| { | |
| public: | |
| SimulatorThreads() = delete; | |
| SimulatorThreads(const SimulatorThreads &) = delete; | |
| SimulatorThreads &operator=(SimulatorThreads &) = delete; | |
| SimulatorThreads(uint32_t num_queues) | |
| : terminate(false), | |
| numQueues(num_queues), | |
| barrier(num_queues) | |
| { | |
| threads.reserve(num_queues); | |
| } | |
| ~SimulatorThreads() | |
| { | |
| // This should only happen after exit has been | |
| // called. Subordinate event queues should normally (assuming | |
| // exit is called from Python) be waiting on the barrier when | |
| // this happens. | |
| // | |
| // N.B.: Not terminating here would make it impossible to | |
| // safely destroy the barrier. | |
| terminateThreads(); | |
| } | |
| void runUntilLocalExit() | |
| { | |
| assert(!terminate); | |
| // Start subordinate threads if needed. | |
| if (threads.empty()) { | |
| // the main thread (the one running Python) handles queue 0, | |
| // so we only need to allocate new threads for queues 1..N-1. | |
| // We'll call these the "subordinate" threads. | |
| for (uint32_t i = 1; i < numQueues; i++) { | |
| threads.emplace_back( | |
| [this](EventQueue *eq) { | |
| thread_main(eq); | |
| }, mainEventQueue[i]); | |
| } | |
| } | |
| // This method is called from the main thread. All subordinate | |
| // threads should be waiting on the barrier when the function | |
| // is called. The arrival of the main thread here will satisfy | |
| // the barrier and start another iteration in the thread loop. | |
| barrier.wait(); | |
| } | |
| void | |
| terminateThreads() | |
| { | |
| assert(!terminate); | |
| if (threads.empty()) | |
| return; | |
| /* This function should only be called when the simulator is | |
| * handling a global exit event (typically from Python). This | |
| * means that the helper threads will be waiting on the | |
| * barrier. Tell the helper threads to exit and release them from | |
| * their barrier. */ | |
| terminate = true; | |
| barrier.wait(); | |
| /* Wait for all of the threads to terminate */ | |
| for (auto &t : threads) { | |
| t.join(); | |
| } | |
| terminate = false; | |
| threads.clear(); | |
| } | |
| protected: | |
| /** | |
| * The main function for all subordinate threads (i.e., all threads | |
| * other than the main thread). These threads start by waiting on | |
| * threadBarrier. Once all threads have arrived at threadBarrier, | |
| * they enter the simulation loop concurrently. When they exit the | |
| * loop, they return to waiting on threadBarrier. This process is | |
| * repeated until the simulation terminates. | |
| */ | |
| void | |
| thread_main(EventQueue *queue) | |
| { | |
| /* Wait for all initialisation to complete */ | |
| barrier.wait(); | |
| while (!terminate) { | |
| doSimLoop(queue); | |
| barrier.wait(); | |
| } | |
| } | |
| std::atomic<bool> terminate; | |
| uint32_t numQueues; | |
| std::vector<std::thread> threads; | |
| Barrier barrier; | |
| }; | |
| static std::unique_ptr<SimulatorThreads> simulatorThreads; | |
| struct DescheduleDeleter | |
| { | |
| void operator()(BaseGlobalEvent *event) | |
| { | |
| if (!event) | |
| return; | |
| event->deschedule(); | |
| delete event; | |
| } | |
| }; | |
| /** Simulate for num_cycles additional cycles. If num_cycles is -1 | |
| * (the default), we simulate to MAX_TICKS unless the max ticks has been set | |
| * via the 'set_max_tick' function prior. This function is exported to Python. | |
| * @return The SimLoopExitEvent that caused the loop to exit. | |
| */ | |
| GlobalSimLoopExitEvent *global_exit_event= nullptr; | |
| GlobalSimLoopExitEvent * | |
| simulate(Tick num_cycles) | |
| { | |
| if (global_exit_event)//cleaning last global exit event | |
| global_exit_event->clean(); | |
| std::unique_ptr<GlobalSyncEvent, DescheduleDeleter> quantum_event; | |
| inform("Entering event queue @ %d. Starting simulation...\n", curTick()); | |
| if (!simulatorThreads) | |
| simulatorThreads.reset(new SimulatorThreads(numMainEventQueues)); | |
| if (!simulate_limit_event) { | |
| // If the simulate_limit_event is not set, we set it to MaxTick. | |
| set_max_tick(MaxTick); | |
| } | |
| if (num_cycles != -1) { | |
| // If the user has specified an exit event after X cycles, do so here. | |
| // Note: This will override any prior set max_tick behaviour (such as | |
| // that above when it is set to MAxTick). | |
| const Tick max_tick = num_cycles < MaxTick - curTick() ? | |
| curTick() + num_cycles : MaxTick; | |
| // This is kept to `set_max_tick` instead of `schedule_tick_exit` to | |
| // preserve backwards functionality. It may be better to deprecate this | |
| // behaviour at some point in favor of `schedule_tick_exit`. | |
| set_max_tick(max_tick); | |
| } | |
| if (numMainEventQueues > 1) { | |
| fatal_if(simQuantum == 0, | |
| "Quantum for multi-eventq simulation not specified"); | |
| quantum_event.reset( | |
| new GlobalSyncEvent(curTick() + simQuantum, simQuantum, | |
| EventBase::Progress_Event_Pri, 0)); | |
| inParallelMode = true; | |
| } | |
| simulatorThreads->runUntilLocalExit(); | |
| Event *local_event = doSimLoop(mainEventQueue[0]); | |
| assert(local_event); | |
| inParallelMode = false; | |
| // locate the global exit event and return it to Python | |
| BaseGlobalEvent *global_event = local_event->globalEvent(); | |
| assert(global_event); | |
| global_exit_event = | |
| dynamic_cast<GlobalSimLoopExitEvent *>(global_event); | |
| assert(global_exit_event); | |
| return global_exit_event; | |
| } | |
| void set_max_tick(Tick tick) | |
| { | |
| if (!simulate_limit_event) { | |
| simulate_limit_event = new GlobalSimLoopExitEvent( | |
| mainEventQueue[0]->getCurTick(), | |
| "simulate() limit reached", 0); | |
| } | |
| simulate_limit_event->reschedule(tick); | |
| } | |
| Tick get_max_tick() | |
| { | |
| if (!simulate_limit_event) { | |
| /* If the GlobalSimLoopExitEvent has not been setup, the maximum tick | |
| * is `MaxTick` as declared in "src/base/types.hh". | |
| */ | |
| return MaxTick; | |
| } | |
| return simulate_limit_event->when(); | |
| } | |
| void | |
| terminateEventQueueThreads() | |
| { | |
| simulatorThreads->terminateThreads(); | |
| } | |
| /** | |
| * The main per-thread simulation loop. This loop is executed by all | |
| * simulation threads (the main thread and the subordinate threads) in | |
| * parallel. | |
| */ | |
| Event * | |
| doSimLoop(EventQueue *eventq) | |
| { | |
| // set the per thread current eventq pointer | |
| curEventQueue(eventq); | |
| eventq->handleAsyncInsertions(); | |
| bool mainQueue = eventq == getEventQueue(0); | |
| while (1) { | |
| // there should always be at least one event (the SimLoopExitEvent | |
| // we just scheduled) in the queue | |
| assert(!eventq->empty()); | |
| assert(curTick() <= eventq->nextTick() && | |
| "event scheduled in the past"); | |
| if (mainQueue && async_event) { | |
| async_event = false; | |
| // Take the event queue lock in case any of the service | |
| // routines want to schedule new events. | |
| std::lock_guard<EventQueue> lock(*eventq); | |
| if (async_statdump || async_statreset) { | |
| statistics::schedStatEvent(async_statdump, async_statreset); | |
| async_statdump = false; | |
| async_statreset = false; | |
| } | |
| if (async_io) { | |
| async_io = false; | |
| pollQueue.service(); | |
| } | |
| if (async_exit) { | |
| async_exit = false; | |
| exitSimLoop("user interrupt received"); | |
| } | |
| if (async_exception) { | |
| async_exception = false; | |
| return NULL; | |
| } | |
| } | |
| Event *exit_event = eventq->serviceOne(); | |
| if (exit_event != NULL) { | |
| return exit_event; | |
| } | |
| } | |
| // not reached... only exit is return on SimLoopExitEvent | |
| } | |
| } // namespace gem5 |