/* * 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/process.hh" #include "systemc/core/event.hh" #include "systemc/core/port.hh" #include "systemc/core/scheduler.hh" #include "systemc/ext/core/messages.hh" #include "systemc/ext/core/sc_join.hh" #include "systemc/ext/core/sc_main.hh" #include "systemc/ext/core/sc_process_handle.hh" #include "systemc/ext/utils/sc_report_handler.hh" namespace sc_gem5 { class UnwindExceptionReset : public ::sc_core::sc_unwind_exception { public: UnwindExceptionReset() { _isReset = true; } }; class UnwindExceptionKill : public ::sc_core::sc_unwind_exception { public: UnwindExceptionKill() {} }; template struct BuiltinExceptionWrapper : public ExceptionWrapperBase { public: T t; void throw_it() override { throw t; } }; BuiltinExceptionWrapper resetException; BuiltinExceptionWrapper killException; void Process::forEachKid(const std::function &work) { for (auto &kid: get_child_objects()) { Process *p_kid = dynamic_cast(kid); if (p_kid) work(p_kid); } } void Process::suspend(bool inc_kids) { if (inc_kids) forEachKid([](Process *p) { p->suspend(true); }); if (!_suspended && !_terminated) { _suspended = true; _suspendedReady = scheduler.suspend(this); if (procKind() != ::sc_core::SC_METHOD_PROC_ && scheduler.current() == this) { // This isn't in the spec, but Accellera says that a thread that // self suspends should be marked ready immediately when it's // resumed. _suspendedReady = true; scheduler.yield(); } } } void Process::resume(bool inc_kids) { if (inc_kids) forEachKid([](Process *p) { p->resume(true); }); if (_suspended && !_terminated) { _suspended = false; if (_suspendedReady) scheduler.resume(this); _suspendedReady = false; } } void Process::disable(bool inc_kids) { if (inc_kids) forEachKid([](Process *p) { p->disable(true); }); if (!::sc_core::sc_allow_process_control_corners && timeoutEvent.scheduled()) { std::string message("attempt to disable a thread with timeout wait: "); message += name(); SC_REPORT_ERROR(sc_core::SC_ID_PROCESS_CONTROL_CORNER_CASE_, message.c_str()); } if (!_terminated) _disabled = true; } void Process::enable(bool inc_kids) { if (inc_kids) forEachKid([](Process *p) { p->enable(true); }); if (!_terminated) _disabled = false; } void Process::kill(bool inc_kids) { if (::sc_core::sc_get_status() != ::sc_core::SC_RUNNING) { SC_REPORT_ERROR(sc_core::SC_ID_KILL_PROCESS_WHILE_UNITIALIZED_, name()); } // Propogate the kill to our children no matter what happens to us. if (inc_kids) forEachKid([](Process *p) { p->kill(true); }); // If we're unwinding or terminated, ignore the kill request. if (_isUnwinding || _terminated) return; // Update our state. terminate(); _isUnwinding = true; // Make sure this process isn't marked ready popListNode(); // Inject the kill exception into this process if it's started. if (!_needsStart) injectException(killException); } void Process::reset(bool inc_kids) { if (::sc_core::sc_get_status() != ::sc_core::SC_RUNNING) { SC_REPORT_ERROR(sc_core::SC_ID_RESET_PROCESS_WHILE_NOT_RUNNING_, name()); } // Propogate the reset to our children no matter what happens to us. if (inc_kids) forEachKid([](Process *p) { p->reset(true); }); // If we're already unwinding or terminated, ignore the reset request. if (_isUnwinding || _terminated) return; // Clear suspended ready since we're about to run regardless. _suspendedReady = false; _resetEvent.notify(); if (_needsStart) { scheduler.runNow(this); } else { _isUnwinding = true; injectException(resetException); } } void Process::throw_it(ExceptionWrapperBase &exc, bool inc_kids) { if (::sc_core::sc_get_status() != ::sc_core::SC_RUNNING) SC_REPORT_ERROR(sc_core::SC_ID_THROW_IT_WHILE_NOT_RUNNING_, name()); if (inc_kids) forEachKid([&exc](Process *p) { p->throw_it(exc, true); }); if (_needsStart || _terminated || procKind() == ::sc_core::SC_METHOD_PROC_) { SC_REPORT_WARNING(sc_core::SC_ID_THROW_IT_IGNORED_, name()); return; } injectException(exc); } void Process::injectException(ExceptionWrapperBase &exc) { excWrapper = &exc; scheduler.runNow(this); }; void Process::syncResetOn(bool inc_kids) { if (inc_kids) forEachKid([](Process *p) { p->syncResetOn(true); }); _syncReset = true; } void Process::syncResetOff(bool inc_kids) { if (inc_kids) forEachKid([](Process *p) { p->syncResetOff(true); }); _syncReset = false; } void Process::signalReset(bool set, bool sync) { if (set) { waitCount(0); if (sync) { syncResetCount++; } else { asyncResetCount++; cancelTimeout(); clearDynamic(); scheduler.runNext(this); } } else { if (sync) syncResetCount--; else asyncResetCount--; } } void Process::run() { bool reset; do { reset = false; try { func->call(); } catch(ScHalt) { std::cout << "Terminating process " << name() << std::endl; } catch(const ::sc_core::sc_unwind_exception &exc) { reset = exc.is_reset(); _isUnwinding = false; } catch (...) { throw; } } while (reset); needsStart(true); } void Process::addStatic(StaticSensitivity *s) { staticSensitivities.push_back(s); } void Process::setDynamic(DynamicSensitivity *s) { if (dynamicSensitivity) { dynamicSensitivity->clear(); delete dynamicSensitivity; } dynamicSensitivity = s; } void Process::addReset(Reset *reset) { resets.push_back(reset); } void Process::cancelTimeout() { if (timeoutEvent.scheduled()) scheduler.deschedule(&timeoutEvent); } void Process::setTimeout(::sc_core::sc_time t) { cancelTimeout(); scheduler.schedule(&timeoutEvent, t); } void Process::timeout() { // A process is considered timed_out only if it was also waiting for an // event but got a timeout instead. _timedOut = (dynamicSensitivity != nullptr); setDynamic(nullptr); if (disabled()) return; ready(); } void Process::satisfySensitivity(Sensitivity *s) { if (_waitCount) { _waitCount--; return; } // If there's a dynamic sensitivity and this wasn't it, ignore. if ((dynamicSensitivity || timeoutEvent.scheduled()) && dynamicSensitivity != s) { return; } _timedOut = false; // This sensitivity should already be cleared by this point, or the event // which triggered it will take care of it. delete dynamicSensitivity; dynamicSensitivity = nullptr; cancelTimeout(); ready(); } void Process::ready() { if (disabled()) return; if (suspended()) _suspendedReady = true; else if (!scheduled()) scheduler.ready(this); } void Process::lastReport(::sc_core::sc_report *report) { if (report) { _lastReport = std::unique_ptr<::sc_core::sc_report>( new ::sc_core::sc_report(*report)); } else { _lastReport = nullptr; } } ::sc_core::sc_report *Process::lastReport() const { return _lastReport.get(); } Process::Process(const char *name, ProcessFuncWrapper *func, bool internal) : ::sc_core::sc_process_b(name), excWrapper(nullptr), timeoutEvent([this]() { this->timeout(); }), func(func), _internal(internal), _timedOut(false), _dontInitialize(false), _needsStart(true), _isUnwinding(false), _terminated(false), _scheduled(false), _suspended(false), _disabled(false), _syncReset(false), syncResetCount(0), asyncResetCount(0), _waitCount(0), refCount(0), stackSize(::Fiber::DefaultStackSize), dynamicSensitivity(nullptr) { _dynamic = (::sc_core::sc_get_status() > ::sc_core::SC_BEFORE_END_OF_ELABORATION); _newest = this; } void Process::terminate() { _terminated = true; _suspendedReady = false; _suspended = false; _syncReset = false; clearDynamic(); cancelTimeout(); for (auto s: staticSensitivities) { s->clear(); delete s; } staticSensitivities.clear(); _terminatedEvent.notify(); for (auto jw: joinWaiters) jw->signal(); joinWaiters.clear(); } Process *Process::_newest; void throw_it_wrapper(Process *p, ExceptionWrapperBase &exc, bool inc_kids) { p->throw_it(exc, inc_kids); } void newReset(const sc_core::sc_port_base *pb, Process *p, bool s, bool v) { Port *port = Port::fromPort(pb); port->addReset(new Reset(p, s, v)); } void newReset(const sc_core::sc_signal_in_if *sig, Process *p, bool s, bool v) { Reset *reset = new Reset(p, s, v); if (!reset->install(sig)) delete reset; } } // namespace sc_gem5