| /* | |
| * Copyright 2018 Google, Inc. | |
| * | |
| * 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. | |
| * | |
| * Authors: Gabe Black | |
| */ | |
| #include "systemc/core/scheduler.hh" | |
| #include "base/fiber.hh" | |
| #include "base/logging.hh" | |
| #include "sim/eventq.hh" | |
| #include "sim/sim_exit.hh" | |
| #include "systemc/core/kernel.hh" | |
| #include "systemc/core/sc_main_fiber.hh" | |
| #include "systemc/ext/core/messages.hh" | |
| #include "systemc/ext/core/sc_main.hh" | |
| #include "systemc/ext/utils/sc_report.hh" | |
| #include "systemc/ext/utils/sc_report_handler.hh" | |
| #include "systemc/utils/report.hh" | |
| #include "systemc/utils/tracefile.hh" | |
| namespace sc_gem5 | |
| { | |
| Scheduler::Scheduler() : | |
| eq(nullptr), readyEvent(this, false, ReadyPriority), | |
| pauseEvent(this, false, PausePriority), | |
| stopEvent(this, false, StopPriority), _throwUp(nullptr), | |
| starvationEvent(this, false, StarvationPriority), | |
| _elaborationDone(false), _started(false), _stopNow(false), | |
| _status(StatusOther), maxTick(::MaxTick), | |
| maxTickEvent(this, false, MaxTickPriority), | |
| timeAdvancesEvent(this, false, TimeAdvancesPriority), _numCycles(0), | |
| _changeStamp(0), _current(nullptr), initDone(false), runToTime(true), | |
| runOnce(false) | |
| {} | |
| Scheduler::~Scheduler() | |
| { | |
| // Clear out everything that belongs to us to make sure nobody tries to | |
| // clear themselves out after the scheduler goes away. | |
| clear(); | |
| } | |
| void | |
| Scheduler::clear() | |
| { | |
| // Delta notifications. | |
| while (!deltas.empty()) | |
| deltas.front()->deschedule(); | |
| // Timed notifications. | |
| for (auto &tsp: timeSlots) { | |
| TimeSlot *&ts = tsp.second; | |
| while (!ts->events.empty()) | |
| ts->events.front()->deschedule(); | |
| deschedule(ts); | |
| } | |
| timeSlots.clear(); | |
| // gem5 events. | |
| if (readyEvent.scheduled()) | |
| deschedule(&readyEvent); | |
| if (pauseEvent.scheduled()) | |
| deschedule(&pauseEvent); | |
| if (stopEvent.scheduled()) | |
| deschedule(&stopEvent); | |
| if (starvationEvent.scheduled()) | |
| deschedule(&starvationEvent); | |
| if (maxTickEvent.scheduled()) | |
| deschedule(&maxTickEvent); | |
| if (timeAdvancesEvent.scheduled()) | |
| deschedule(&timeAdvancesEvent); | |
| Process *p; | |
| while ((p = initList.getNext())) | |
| p->popListNode(); | |
| while ((p = readyListMethods.getNext())) | |
| p->popListNode(); | |
| while ((p = readyListThreads.getNext())) | |
| p->popListNode(); | |
| Channel *c; | |
| while ((c = updateList.getNext())) | |
| c->popListNode(); | |
| } | |
| void | |
| Scheduler::initPhase() | |
| { | |
| runUpdate(); | |
| for (Process *p = initList.getNext(); p; p = initList.getNext()) { | |
| p->popListNode(); | |
| if (p->dontInitialize()) { | |
| if (!p->hasStaticSensitivities() && !p->internal()) { | |
| SC_REPORT_WARNING(sc_core::SC_ID_DISABLE_WILL_ORPHAN_PROCESS_, | |
| p->name()); | |
| } | |
| } else { | |
| p->ready(); | |
| } | |
| } | |
| runDelta(); | |
| for (auto ets: eventsToSchedule) | |
| eq->schedule(ets.first, ets.second); | |
| eventsToSchedule.clear(); | |
| if (_started) { | |
| if (!runToTime && starved()) | |
| scheduleStarvationEvent(); | |
| kernel->status(::sc_core::SC_RUNNING); | |
| } | |
| initDone = true; | |
| status(StatusOther); | |
| scheduleTimeAdvancesEvent(); | |
| } | |
| void | |
| Scheduler::reg(Process *p) | |
| { | |
| if (initDone) { | |
| // If not marked as dontInitialize, mark as ready. | |
| if (!p->dontInitialize()) | |
| p->ready(); | |
| } else { | |
| // Otherwise, record that this process should be initialized once we | |
| // get there. | |
| initList.pushLast(p); | |
| } | |
| } | |
| void | |
| Scheduler::yield() | |
| { | |
| // Pull a process from the active list. | |
| _current = getNextReady(); | |
| if (!_current) { | |
| // There are no more processes, so return control to evaluate. | |
| Fiber::primaryFiber()->run(); | |
| } else { | |
| _current->popListNode(); | |
| _current->scheduled(false); | |
| // Switch to whatever Fiber is supposed to run this process. All | |
| // Fibers which aren't running should be parked at this line. | |
| _current->fiber()->run(); | |
| // If the current process needs to be manually started, start it. | |
| if (_current && _current->needsStart()) { | |
| _current->needsStart(false); | |
| // If a process hasn't started yet, "resetting" it just starts it | |
| // and signals its reset event. | |
| if (_current->inReset()) | |
| _current->resetEvent().notify(); | |
| try { | |
| _current->run(); | |
| } catch (...) { | |
| throwUp(); | |
| } | |
| } | |
| } | |
| if (_current && !_current->needsStart()) { | |
| if (_current->excWrapper) { | |
| auto ew = _current->excWrapper; | |
| _current->excWrapper = nullptr; | |
| ew->throw_it(); | |
| } else if (_current->inReset()) { | |
| _current->reset(false); | |
| } | |
| } | |
| } | |
| void | |
| Scheduler::ready(Process *p) | |
| { | |
| if (_stopNow) | |
| return; | |
| p->scheduled(true); | |
| if (p->procKind() == ::sc_core::SC_METHOD_PROC_) | |
| readyListMethods.pushLast(p); | |
| else | |
| readyListThreads.pushLast(p); | |
| if (!inEvaluate()) | |
| scheduleReadyEvent(); | |
| } | |
| void | |
| Scheduler::resume(Process *p) | |
| { | |
| if (initDone) | |
| ready(p); | |
| else | |
| initList.pushLast(p); | |
| } | |
| bool | |
| listContains(ListNode *list, ListNode *target) | |
| { | |
| ListNode *n = list->nextListNode; | |
| while (n != list) | |
| if (n == target) | |
| return true; | |
| return false; | |
| } | |
| bool | |
| Scheduler::suspend(Process *p) | |
| { | |
| bool was_ready; | |
| if (initDone) { | |
| // After initialization, check if we're on a ready list. | |
| was_ready = (p->nextListNode != nullptr); | |
| p->popListNode(); | |
| } else { | |
| // Nothing is ready before init. | |
| was_ready = false; | |
| } | |
| return was_ready; | |
| } | |
| void | |
| Scheduler::requestUpdate(Channel *c) | |
| { | |
| updateList.pushLast(c); | |
| if (!inEvaluate()) | |
| scheduleReadyEvent(); | |
| } | |
| void | |
| Scheduler::asyncRequestUpdate(Channel *c) | |
| { | |
| std::lock_guard<std::mutex> lock(asyncListMutex); | |
| asyncUpdateList.pushLast(c); | |
| } | |
| void | |
| Scheduler::scheduleReadyEvent() | |
| { | |
| // Schedule the evaluate and update phases. | |
| if (!readyEvent.scheduled()) { | |
| schedule(&readyEvent); | |
| if (starvationEvent.scheduled()) | |
| deschedule(&starvationEvent); | |
| } | |
| } | |
| void | |
| Scheduler::scheduleStarvationEvent() | |
| { | |
| if (!starvationEvent.scheduled()) { | |
| schedule(&starvationEvent); | |
| if (readyEvent.scheduled()) | |
| deschedule(&readyEvent); | |
| } | |
| } | |
| void | |
| Scheduler::runReady() | |
| { | |
| scheduleTimeAdvancesEvent(); | |
| bool empty = readyListMethods.empty() && readyListThreads.empty(); | |
| lastReadyTick = getCurTick(); | |
| // The evaluation phase. | |
| status(StatusEvaluate); | |
| do { | |
| yield(); | |
| } while (getNextReady()); | |
| _current = nullptr; | |
| if (!empty) { | |
| _numCycles++; | |
| _changeStamp++; | |
| } | |
| if (_stopNow) { | |
| status(StatusOther); | |
| return; | |
| } | |
| runUpdate(); | |
| if (!traceFiles.empty()) | |
| trace(true); | |
| runDelta(); | |
| if (!runToTime && starved()) | |
| scheduleStarvationEvent(); | |
| if (runOnce) | |
| schedulePause(); | |
| status(StatusOther); | |
| } | |
| void | |
| Scheduler::runUpdate() | |
| { | |
| status(StatusUpdate); | |
| { | |
| std::lock_guard<std::mutex> lock(asyncListMutex); | |
| Channel *channel; | |
| while ((channel = asyncUpdateList.getNext()) != nullptr) | |
| updateList.pushLast(channel); | |
| } | |
| try { | |
| Channel *channel = updateList.getNext(); | |
| while (channel) { | |
| channel->popListNode(); | |
| channel->update(); | |
| channel = updateList.getNext(); | |
| } | |
| } catch (...) { | |
| throwUp(); | |
| } | |
| } | |
| void | |
| Scheduler::runDelta() | |
| { | |
| status(StatusDelta); | |
| try { | |
| while (!deltas.empty()) | |
| deltas.back()->run(); | |
| } catch (...) { | |
| throwUp(); | |
| } | |
| } | |
| void | |
| Scheduler::pause() | |
| { | |
| status(StatusPaused); | |
| kernel->status(::sc_core::SC_PAUSED); | |
| runOnce = false; | |
| if (scMainFiber.called()) { | |
| if (!scMainFiber.finished()) | |
| scMainFiber.run(); | |
| } else { | |
| if (scMainFiber.finished()) | |
| fatal("Pausing systemc after sc_main completed."); | |
| else | |
| exitSimLoopNow("systemc pause"); | |
| } | |
| } | |
| void | |
| Scheduler::stop() | |
| { | |
| status(StatusStopped); | |
| kernel->stop(); | |
| clear(); | |
| runOnce = false; | |
| if (scMainFiber.called()) { | |
| if (!scMainFiber.finished()) | |
| scMainFiber.run(); | |
| } else { | |
| if (scMainFiber.finished()) | |
| fatal("Stopping systemc after sc_main completed."); | |
| else | |
| exitSimLoopNow("systemc stop"); | |
| } | |
| } | |
| void | |
| Scheduler::start(Tick max_tick, bool run_to_time) | |
| { | |
| _started = true; | |
| status(StatusOther); | |
| runToTime = run_to_time; | |
| maxTick = max_tick; | |
| lastReadyTick = getCurTick(); | |
| if (initDone) { | |
| if (!runToTime && starved()) | |
| scheduleStarvationEvent(); | |
| kernel->status(::sc_core::SC_RUNNING); | |
| } | |
| schedule(&maxTickEvent, maxTick); | |
| scheduleTimeAdvancesEvent(); | |
| // Return to gem5 to let it run events, etc. | |
| Fiber::primaryFiber()->run(); | |
| if (pauseEvent.scheduled()) | |
| deschedule(&pauseEvent); | |
| if (stopEvent.scheduled()) | |
| deschedule(&stopEvent); | |
| if (maxTickEvent.scheduled()) | |
| deschedule(&maxTickEvent); | |
| if (starvationEvent.scheduled()) | |
| deschedule(&starvationEvent); | |
| if (_throwUp) { | |
| const ::sc_core::sc_report *to_throw = _throwUp; | |
| _throwUp = nullptr; | |
| throw *to_throw; | |
| } | |
| } | |
| void | |
| Scheduler::oneCycle() | |
| { | |
| runOnce = true; | |
| scheduleReadyEvent(); | |
| start(::MaxTick, false); | |
| } | |
| void | |
| Scheduler::schedulePause() | |
| { | |
| if (pauseEvent.scheduled()) | |
| return; | |
| schedule(&pauseEvent); | |
| } | |
| void | |
| Scheduler::throwUp() | |
| { | |
| if (scMainFiber.called() && !scMainFiber.finished()) { | |
| ::sc_core::sc_report report = reportifyException(); | |
| _throwUp = &report; | |
| status(StatusOther); | |
| scMainFiber.run(); | |
| } else { | |
| reportHandlerProc(reportifyException(), | |
| ::sc_core::sc_report_handler::get_catch_actions()); | |
| } | |
| } | |
| void | |
| Scheduler::scheduleStop(bool finish_delta) | |
| { | |
| if (stopEvent.scheduled()) | |
| return; | |
| if (!finish_delta) { | |
| _stopNow = true; | |
| // If we're not supposed to finish the delta cycle, flush all | |
| // pending activity. | |
| clear(); | |
| } | |
| schedule(&stopEvent); | |
| } | |
| void | |
| Scheduler::trace(bool delta) | |
| { | |
| for (auto tf: traceFiles) | |
| tf->trace(delta); | |
| } | |
| Scheduler scheduler; | |
| Process *getCurrentProcess() { return scheduler.current(); } | |
| namespace { | |
| void | |
| throwingReportHandler(const ::sc_core::sc_report &r, | |
| const ::sc_core::sc_actions &) | |
| { | |
| throw r; | |
| } | |
| } // anonymous namespace | |
| const ::sc_core::sc_report | |
| reportifyException() | |
| { | |
| ::sc_core::sc_report_handler_proc old_handler = reportHandlerProc; | |
| ::sc_core::sc_report_handler::set_handler(&throwingReportHandler); | |
| try { | |
| try { | |
| // Rethrow the current exception so we can catch it and throw an | |
| // sc_report instead if it's not a type we recognize/can handle. | |
| throw; | |
| } catch (const ::sc_core::sc_report &) { | |
| // It's already a sc_report, so nothing to do. | |
| throw; | |
| } catch (const ::sc_core::sc_unwind_exception &) { | |
| panic("Kill/reset exception escaped a Process::run()"); | |
| } catch (const std::exception &e) { | |
| SC_REPORT_ERROR( | |
| sc_core::SC_ID_SIMULATION_UNCAUGHT_EXCEPTION_, e.what()); | |
| } catch (const char *msg) { | |
| SC_REPORT_ERROR( | |
| sc_core::SC_ID_SIMULATION_UNCAUGHT_EXCEPTION_, msg); | |
| } catch (...) { | |
| SC_REPORT_ERROR( | |
| sc_core::SC_ID_SIMULATION_UNCAUGHT_EXCEPTION_, | |
| "UNKNOWN EXCEPTION"); | |
| } | |
| } catch (const ::sc_core::sc_report &r) { | |
| ::sc_core::sc_report_handler::set_handler(old_handler); | |
| return r; | |
| } | |
| panic("No exception thrown in reportifyException."); | |
| } | |
| } // namespace sc_gem5 |