| /* | |
| * 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 <typename T> | |
| struct BuiltinExceptionWrapper : public ExceptionWrapperBase | |
| { | |
| public: | |
| T t; | |
| void throw_it() override { throw t; } | |
| }; | |
| BuiltinExceptionWrapper<UnwindExceptionReset> resetException; | |
| BuiltinExceptionWrapper<UnwindExceptionKill> killException; | |
| void | |
| Process::forEachKid(const std::function<void(Process *)> &work) | |
| { | |
| for (auto &kid: get_child_objects()) { | |
| Process *p_kid = dynamic_cast<Process *>(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<bool> *sig, Process *p, bool s, bool v) | |
| { | |
| Reset *reset = new Reset(p, s, v); | |
| if (!reset->install(sig)) | |
| delete reset; | |
| } | |
| } // namespace sc_gem5 |