diff --git a/src/common/util/defer.h b/src/common/util/defer.h new file mode 100644 index 000000000..233a9f59c --- /dev/null +++ b/src/common/util/defer.h @@ -0,0 +1,95 @@ +/** +Copyright (c) 2015-2018, Pavlo M, https://github.com/olvap80 +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 deferpp 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 HOLDER 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. +*/ +#ifndef UTIL_DEFER_H +#define UTIL_DEFER_H + +/// Defer following code until enclosing scope is exited +/** Usage: DEFER{ some_code_to_be_deferred }; + Remember that some_code_to_be_deferred shall not allow + exceptions to be propagated out of curly braces */ +#define DEFER const auto DEFER_CAT_ID(callOnScopeExit, __LINE__) = (Util::tagClassForLambda)->*[&]() + +//============================================================================== +// Implementation details follow + +// Helper macro to expand and concatenate macro arguments into combined identifier +#define DEFER_CAT_ID(a, b) DEFER_CAT_ID_EXPANDED_HELPER(a, b) +// helper macro to concatenate expanded macro arguments +#define DEFER_CAT_ID_EXPANDED_HELPER(a, b) a##b + +namespace Util { +/// Helper type to trigger operator ->* +struct TagClassForLambda { + constexpr TagClassForLambda() = default; +}; +/// Use this "instance" to trigger overloaded operator ->* +/** The trick with tagClassForLambda is needed + to infer the type of the lambda */ +constexpr TagClassForLambda tagClassForLambda; + +/// RAII for implementing DEFER behavior +template struct CallOnScopeExit { + /// Create RAII wrapper around Lambda + /** Using Lambda directly, optimizer takes case due to [&]() in front */ + constexpr CallOnScopeExit(Lambda initialLambda) + : lambda(initialLambda) + , isOwner(true) {} + + /// Usually optimized away due to RVO + CallOnScopeExit(CallOnScopeExit&& other) + : lambda(other.lambda) + , isOwner(true) { + other.isOwner = false; + } + + // ensure copy changes go only through move constructor + CallOnScopeExit(const CallOnScopeExit& other) = delete; + CallOnScopeExit& operator=(const CallOnScopeExit& other) = delete; + + /// Actual lambda call once CallOnScopeExit goes out of scope + ~CallOnScopeExit() { + if(isOwner) { // condition is usually optimized away + lambda(); + } + } + +private: + const Lambda lambda; ///< Hold lambda, avoid slow std::function here + bool isOwner; ///< Ensure 100% lambda is called only one time +}; + +/// Helper operator to easy catch lambda for DEFER macro +/** Use template to avoid slow std::function + (raw lambda is copied/stored here) */ +template constexpr CallOnScopeExit operator->*(const TagClassForLambda&, Lambda lambda) { + return CallOnScopeExit(lambda); +} +} // namespace Util +#endif // UTIL_DEFER_H \ No newline at end of file diff --git a/src/sysc/CMakeLists.txt b/src/sysc/CMakeLists.txt index 6a2293a71..f2c75a63d 100644 --- a/src/sysc/CMakeLists.txt +++ b/src/sysc/CMakeLists.txt @@ -51,7 +51,10 @@ set(LIB_SOURCES ) if(DEFINED SC_VERSION_MAJOR AND SC_VERSION_MAJOR GREATER 2) - list(APPEND LIB_SOURCES tlm/scc/quantum_keeper.cpp) + list(APPEND LIB_SOURCES + tlm/scc/qk/global_time_keeper.cpp + tlm/scc/qk/sc_time_syncronizer.cpp + ) endif() if(ENABLE_PYTHON4SC) find_package(pybind11 QUIET) diff --git a/src/sysc/tlm/scc/qk/global_time_keeper.cpp b/src/sysc/tlm/scc/qk/global_time_keeper.cpp new file mode 100644 index 000000000..77051df74 --- /dev/null +++ b/src/sysc/tlm/scc/qk/global_time_keeper.cpp @@ -0,0 +1,94 @@ +#include "global_time_keeper.h" +#include + +namespace tlm { +namespace scc { +namespace qk { +/////////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////////// +global_time_keeper::global_time_keeper() = default; + +global_time_keeper::~global_time_keeper() { + // shutting down time keeper thread + stop_it.store(true, std::memory_order_acq_rel); + update.notify_all(); +} +// this function will be called from the systemc thread +void global_time_keeper::start() { + std::thread t{&global_time_keeper::sync_local_times, this}; + t.detach(); + started = true; +} +// this function will be called from the systemc thread +size_t global_time_keeper::get_channel_index() { + if(started) + throw std::runtime_error("global_time_keeper already started"); + client_coms_channels.emplace_back(client_coms_channels.size()); + update_it = true; + return client_coms_channels.size() - 1; +} + +// runs in a background thread +void global_time_keeper::sync_local_times() { + std::unique_lock lock{upd_mtx}; + while(true) { + update.wait_for(lock, std::chrono::milliseconds(1), + [this]() -> bool { return update_it.load(std::memory_order_relaxed) || stop_it.load(std::memory_order_relaxed); }); + if(update_it.exchange(false)) { +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL("global_time_keeper::sync_local_times") << "update loop"; +#endif + uint64_t min_local_time = std::numeric_limits::max(); + bool tail = false; + while(auto res = sc_coms_channel.client2time_keeper.front()) { + sc_coms_channel.thread_local_time = res->time_tick; + sc_coms_channel.client2time_keeper.pop(); + } + for(size_t i = 0; i < client_coms_channels.size(); ++i) { + auto& client_coms_channel = client_coms_channels[i]; + bool has_task = false; + bool has_entries = false; + while(auto res = client_coms_channel.client2time_keeper.front()) { + has_entries = true; + client_coms_channel.thread_local_time = res->time_tick; + if(res->task.valid()) { +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL("global_time_keeper::sync_local_times") + << "forwarding task of client " << client_coms_channel.my_id << " with timestamp t=" << res->time_tick; +#endif + pending_tasks.emplace(client_coms_channel.my_id, res->time_tick, std::move(res->task)); + has_task = true; + } + client_coms_channel.client2time_keeper.pop(); + } + if(has_entries) + client_coms_channel.waiting4sc = has_task; + if(!client_coms_channel.waiting4sc) + min_local_time = std::min(client_coms_channel.thread_local_time, min_local_time); +#ifdef DEBUG_MT_SCHEDULING + if(has_entries) { + SCCTRACEALL("global_time_keeper::sync_local_times") + << "thread_local_time[" << i << "]=" << sc_core::sc_time::from_value(client_coms_channel.thread_local_time) + << (client_coms_channel.waiting4sc ? " (waiting)" : " (running)"); + } +#endif + } + window_min_time = min_local_time; + sc_coms_channel.time_keeper2client.store(min_local_time); + auto window_max_time = min_local_time + std::max(tlm::tlm_global_quantum::instance().get().value(), sc_time_step.value()); + for(auto& client_coms_channel : client_coms_channels) { + client_coms_channel.time_keeper2client.store(window_max_time); + } +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL("global_time_keeper::sync_local_times") << "window_min_time=" << window_min_time; +#endif + } else if(stop_it.load()) { + break; + } + } +} + +} // namespace qk +} // namespace scc +} // namespace tlm \ No newline at end of file diff --git a/src/sysc/tlm/scc/qk/global_time_keeper.h b/src/sysc/tlm/scc/qk/global_time_keeper.h new file mode 100644 index 000000000..61454d119 --- /dev/null +++ b/src/sysc/tlm/scc/qk/global_time_keeper.h @@ -0,0 +1,125 @@ +#ifndef __SCC_TLM_QK_GLOBAL_TIME_KEEPER_H__ +#define __SCC_TLM_QK_GLOBAL_TIME_KEEPER_H__ + +#include "types.h" +#include + +namespace tlm { +namespace scc { +namespace qk { +/////////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////////// +struct sc_time_syncronizer; +/** + * @brief the global time keeper, a singleton + * @details The global time keeper receives actual time stamps of all client threads + * and calculates the max time they are alloed to advance beyond systemc. All calculations are done + * based on ticks which is the minimal time step a simulator can do. The SystemC thread is special + * in that it is time wise always the last/slowest thread. + * + */ +struct global_time_keeper { + friend class sc_time_syncronizer; + /** + * @brief the maximum timestep the simulator is allowed to do if there are no future events + * + */ + const sc_core::sc_time sc_time_step = 1_ms; + /** + * @brief the singleton getter + * + * @return global_time_keeper& + */ + static global_time_keeper& get() { + static global_time_keeper keeper; + return keeper; + } + /** + * @brief Get the minimum time ticks of all client threads (not SystemC) + * + * @return uint64_t the absolute number of ticks + */ + inline uint64_t get_min_time_ticks() { return window_min_time; } + /** + * @brief Get the maximum time ticks a client thread is allowed to advance + * + * @param idx the id of the client thread, to be obtained using get_channel_index() + * @return uint64_t the absolute number of maximum ticks, if no new information it returns -1 + */ + inline uint64_t get_max_time_ticks(size_t idx) { return client_coms_channels[idx].time_keeper2client.load(); } + /** + * @brief updates the global time keeper with the local time ticks of a clinet thread + * + * @param idx the id of the client thread, to be obtained using get_channel_index() + * @param tick the absolute number of actual ticks + */ + inline void update_time_ticks(size_t idx, uint64_t tick) { + client_coms_channels[idx].client2time_keeper.push(std::move(comms_entry{tick, std::move(callback_task())})); + update_it.store(true); + std::unique_lock lk(upd_mtx); + update.notify_all(); + } + /** + * @brief updates the global time keeper with the local time ticks of a clinet thread and schedules a task at this new time point + * + * @param idx the id of the client thread, to be obtained using get_channel_index() + * @param tick the absolute number of actual ticks + * @param task the task to be executed in the SystemC kernel. It shall return the time it used for execution + */ + inline void schedule_task(size_t idx, std::packaged_task&& task, uint64_t when) { + client_coms_channels[idx].client2time_keeper.push(std::move(comms_entry{when, std::move(task)})); + update_it.store(true); + std::unique_lock lk(upd_mtx); + update.notify_all(); + } + /** + * @brief Get the maximum sc time ticks a client thread is allowed to advance + * + * @return uint64_t the absolute number of ticks + */ + inline uint64_t get_max_sc_time_ticks() { return sc_coms_channel.time_keeper2client.load(); } + /** + * @brief updates the global time keeper with the local time ticks of the SystemC thread + * + * @param tick the absolute number of actual SystemC ticks + */ + inline void update_sc_time_ticks(uint64_t tick) { + sc_coms_channel.client2time_keeper.push(std::move(comms_entry{tick, std::move(callback_task())})); +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL("global_time_keeper::update_sc_time_ticks") << "sc_time=" << sc_core::sc_time::from_value(tick); +#endif + update_it.store(true); + std::unique_lock lk(upd_mtx); + update.notify_all(); + } + +protected: + global_time_keeper(); + + global_time_keeper(global_time_keeper const&) = delete; + + global_time_keeper(global_time_keeper&&) = delete; + + ~global_time_keeper(); + + void start(); + + size_t get_channel_index(); + + void sync_local_times(); + + std::atomic stop_it{false}; + std::atomic update_it{false}; + std::atomic_uint64_t window_min_time; + std::mutex upd_mtx; + std::condition_variable update; + thread_comms_channel sc_coms_channel{-1ULL}; + std::deque client_coms_channels; + rigtorp::SPSCQueue> pending_tasks{1024}; + bool started = false; +}; +} // namespace qk +} // namespace scc +} // namespace tlm +#endif // __SCC_TLM_QK_GLOBAL_TIME_KEEPER_H__ diff --git a/src/sysc/tlm/scc/qk/sc_time_syncronizer.cpp b/src/sysc/tlm/scc/qk/sc_time_syncronizer.cpp new file mode 100644 index 000000000..c93cda031 --- /dev/null +++ b/src/sysc/tlm/scc/qk/sc_time_syncronizer.cpp @@ -0,0 +1,109 @@ +#include "sc_time_syncronizer.h" +#include "global_time_keeper.h" +#include +#include + +namespace tlm { +namespace scc { +namespace qk { +sc_time_syncronizer::sc_time_syncronizer(global_time_keeper& gtk) +: gtk(gtk) { + sc_core::sc_register_stage_callback(*this, sc_core::SC_PRE_TIMESTEP | sc_core::SC_POST_END_OF_ELABORATION); + sc_core::sc_spawn_options opt; + opt.spawn_method(); + sc_core::sc_spawn([this]() { method_callback(); }, nullptr, &opt); +} + +void sc_time_syncronizer::method_callback() { + auto res = this->gtk.get_max_sc_time_ticks(); + if(res != std::numeric_limits::max()) { + sc_max_time.store(res, std::memory_order_seq_cst); + } + auto& pending_tasks = this->gtk.pending_tasks; + if(pending_tasks.size()) { +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL(__PRETTY_FUNCTION__) << "updating pending tasks"; +#endif + while(pending_tasks.size()) { + auto res = pending_tasks.front(); + auto idx = std::get<0>(*res); + auto t = std::get<1>(*res); + auto& peq = sc_task_que[idx]; + if(t > sc_core::sc_time_stamp().value()) { + auto d = sc_core::sc_time::from_value(t) - sc_core::sc_time_stamp(); +#ifdef DEBUG_MT_SCHEDULING + SCCDEBUG(__PRETTY_FUNCTION__) << "scheduling task from client " << idx << " with t=" << t << " with delay " << d; +#endif + peq.notify(std::move(std::get<2>(*res)), d); + } else { +#ifdef DEBUG_MT_SCHEDULING + SCCDEBUG(__PRETTY_FUNCTION__) << "scheduling task from client " << idx << " with t=" << t << " now"; +#endif + peq.notify(std::move(std::get<2>(*res)), sc_core::SC_ZERO_TIME); + } + pending_tasks.pop(); + } + sc_core::next_trigger(sc_core::SC_ZERO_TIME); + } else if(sc_core::sc_get_curr_simcontext()->pending_activity_at_current_time()) { +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL(__PRETTY_FUNCTION__) << "yield to next delta cycle"; +#endif + sc_core::next_trigger(sc_core::SC_ZERO_TIME); + } else { + auto time_to_next_evt = sc_core::sc_time_to_pending_activity(sc_core::sc_get_curr_simcontext()); + if(!sc_is_free_running) { + auto min_time = sc_core::sc_time::from_value(gtk.get_max_sc_time_ticks()); + auto abs_time_to_next_evt = sc_core::sc_time_stamp() + time_to_next_evt; + if(min_time < abs_time_to_next_evt) { + if(min_time > sc_core::sc_time_stamp()) { + sc_core::next_trigger(min_time - sc_core::sc_time_stamp()); + } else { + // slow down systemc execution to be the slower than client threads + std::this_thread::yield(); + // std::this_thread::sleep_for(std::chrono::microseconds{1}); + sc_core::next_trigger(sc_core::SC_ZERO_TIME); // play it again, Sam + } + } else { +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL(__PRETTY_FUNCTION__) << "advancing SC time lockstepped to " << abs_time_to_next_evt; +#endif + sc_core::next_trigger(time_to_next_evt); + } + } else { + // all threads are blocked by SystemC, so we can run freely but to avoid starving the kernel we only proceed to the + // time of the slowest client thread as this one is waiting to be served + auto next_time_point = sc_core::sc_time_stamp() + time_to_next_evt; + if(next_time_point.value() == std::numeric_limits::max()) { + time_to_next_evt = tlm_utils::tlm_quantumkeeper::get_global_quantum(); + if(time_to_next_evt == sc_core::SC_ZERO_TIME) + time_to_next_evt = 1_us; + } +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL(__PRETTY_FUNCTION__) << "advancing SC time free running to " << next_time_point; +#endif + sc_core::next_trigger(time_to_next_evt); + } + } +} + +void sc_time_syncronizer::stage_callback(const sc_core::sc_stage& stage) { + switch(stage) { + case sc_core::SC_PRE_TIMESTEP: { + sc_core::sc_time next_time; + if(sc_core::sc_get_curr_simcontext()->next_time(next_time)) { + gtk.update_sc_time_ticks(next_time.value()); + } +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL(__PRETTY_FUNCTION__) << "advancing SystemC kernel time to " << next_time << ", get_min_time()=" << get_min_time(); +#endif + } break; + case sc_core::SC_POST_END_OF_ELABORATION: + gtk.start(); + break; + default: + break; + } +} +} // namespace qk +} // namespace scc +} // namespace tlm \ No newline at end of file diff --git a/src/sysc/tlm/scc/qk/sc_time_syncronizer.h b/src/sysc/tlm/scc/qk/sc_time_syncronizer.h new file mode 100644 index 000000000..bfd6b3eb9 --- /dev/null +++ b/src/sysc/tlm/scc/qk/sc_time_syncronizer.h @@ -0,0 +1,84 @@ +#ifndef __SCC_TLM_QK_SC_TIME_SYNCRONIZER_H__ +#define __SCC_TLM_QK_SC_TIME_SYNCRONIZER_H__ + +#include "global_time_keeper.h" +#include + +namespace tlm { +namespace scc { +namespace qk { +/** + *the SystemC time keeper, a singleton in the SystemC thread + */ +struct sc_time_syncronizer : sc_core::sc_stage_callback_if, sc_core::sc_process_host { + ~sc_time_syncronizer() = default; + /** + * @brief the singleton getter + * + * @return sc_time_syncronizer& + */ + static sc_time_syncronizer& get() { + static sc_time_syncronizer keeper(global_time_keeper::get()); + return keeper; + } + /** + * @brief Get the channel index + * + * @details it creates all needed infrastructure to handle the data exchange between client threads and the SystemC thread + * + * @return size_t the index + */ + size_t get_channel_index() { + auto idx = gtk.get_channel_index(); + sc_task_que.emplace_back(); + blocked_channels.emplace_back(true); + auto& sctq = sc_task_que[sc_task_que.size() - 1]; + sc_core::sc_spawn( + [this, &sctq, idx]() { + wait(sc_core::SC_ZERO_TIME); + while(true) { + // while(!sctq.has_next()) { + // sc_core::wait(sctq.event()); + // } + // this->notify_client_blocked(idx, true); + sctq.get()(); + // this->notify_client_blocked(idx, false); + } + }, + sc_core::sc_gen_unique_name("peq_cb", false)); + + return idx; + } + /** + * @brief Get the current sc kernel time + * + * @return sc_core::sc_time + */ + inline sc_core::sc_time get_sc_kernel_time() { return sc_core::sc_time::from_value(sc_max_time.load(std::memory_order_seq_cst)); } + + inline void notify_client_blocked(size_t idx, bool is_blocked) { + assert(idx < blocked_channels.size()); + blocked_channels[idx] = is_blocked; + sc_is_free_running = !std::any_of(std::begin(blocked_channels), std::end(blocked_channels), [](bool b) { return !b; }); +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL(__PRETTY_FUNCTION__) << (is_blocked ? "blocking" : "unblocking") << " thread " << idx + << ", sc_is_free_running=" << sc_is_free_running; +#endif + } + +private: + sc_time_syncronizer(global_time_keeper& gtk); + void method_callback(); + void stage_callback(const sc_core::sc_stage& stage) override; + sc_core::sc_time get_min_time() { return sc_core::sc_time::from_value(gtk.get_min_time_ticks()); } + global_time_keeper& gtk; + sc_core::sc_vector<::scc::peq> sc_task_que; + std::atomic_int64_t sc_max_time{0}; + sc_core::sc_process_handle method_handle; + std::vector blocked_channels; + bool sc_is_free_running{true}; +}; +} // namespace qk +} // namespace scc +} // namespace tlm +#endif // __SCC_TLM_QK_SC_TIME_SYNCRONIZER_H__ diff --git a/src/sysc/tlm/scc/qk/types.h b/src/sysc/tlm/scc/qk/types.h new file mode 100644 index 000000000..eabae72f3 --- /dev/null +++ b/src/sysc/tlm/scc/qk/types.h @@ -0,0 +1,67 @@ +#ifndef __SCC_TLM_QK_TYPES_H__ +#define __SCC_TLM_QK_TYPES_H__ + +#include +#include +#include + +namespace tlm { +namespace scc { +namespace qk { +#ifdef __cpp_lib_hardware_interference_size +static constexpr size_t kCacheLineSize = std::hardware_destructive_interference_size; +#else +static constexpr size_t kCacheLineSize = 64; +#endif +/////////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////////// +using callback_fct = sc_core::sc_time(void); +using callback_task = std::packaged_task; + +struct comms_entry { + uint64_t time_tick; + callback_task task; +}; +/////////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////////// +/** + * @brief the communication channel between client threads (incl. SystemC) and the + * global time keeper + * + */ +struct thread_comms_channel { + /** + * @brief Construct a new thread comms channel object + * + * @param my_id + */ + thread_comms_channel(uint64_t my_id) + : time_keeper2client(0) + , client2time_keeper(7) + , my_id(my_id) {} + /** + * @brief Construct a new thread comms channel object + * + * @param o + */ + thread_comms_channel(thread_comms_channel const& o) + : my_id(o.my_id) + , client2time_keeper(o.client2time_keeper.capacity()) + , time_keeper2client(0) + , thread_local_time(o.thread_local_time) { + assert(o.client2time_keeper.size() == 0); + }; + + alignas(64) std::atomic time_keeper2client; + rigtorp::SPSCQueue client2time_keeper; + + const uint64_t my_id; + bool waiting4sc; + uint64_t thread_local_time; +}; +} // namespace qk +} // namespace scc +} // namespace tlm +#endif // __SCC_TLM_QK_TYPES_H__ diff --git a/src/sysc/tlm/scc/quantum_keeper.cpp b/src/sysc/tlm/scc/quantum_keeper.cpp deleted file mode 100644 index 5eccdcf3c..000000000 --- a/src/sysc/tlm/scc/quantum_keeper.cpp +++ /dev/null @@ -1,185 +0,0 @@ -#include "quantum_keeper.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace tlm { -namespace scc { -/////////////////////////////////////////////////////////////////////////////// -// -/////////////////////////////////////////////////////////////////////////////// -global_time_keeper::global_time_keeper() = default; - -global_time_keeper::~global_time_keeper() { - // shutting down time keeper thread - stop_it.store(true, std::memory_order_acq_rel); - update.notify_all(); -} -// this function will be called from the systemc thread -void global_time_keeper::start() { - std::thread t{&global_time_keeper::sync_local_times, this}; - t.detach(); - started = true; -} -// this function will be called from the systemc thread -size_t global_time_keeper::get_channel_index() { - if(started) - throw std::runtime_error("global_time_keeper already started"); - client_coms_channels.emplace_back(client_coms_channels.size()); - update_it = true; - return client_coms_channels.size() - 1; -} - -// runs in a background thread -void global_time_keeper::sync_local_times() { - std::unique_lock lock{upd_mtx}; - while(true) { - update.wait_for(lock, std::chrono::milliseconds(1), - [this]() -> bool { return update_it.load(std::memory_order_relaxed) || stop_it.load(std::memory_order_relaxed); }); - if(update_it.exchange(false)) { -#ifdef DEBUG_MT_SCHDULING - SCCTRACEALL("global_time_keeper::sync_local_times") << "update loop"; -#endif - uint64_t min_local_time = std::numeric_limits::max(); - bool tail = false; - while(auto res = sc_coms_channel.client2time_keeper.front()) { - sc_coms_channel.thread_local_time = res->time_tick; - sc_coms_channel.client2time_keeper.pop(); - } - for(auto& client_coms_channel : client_coms_channels) { - while(auto res = client_coms_channel.client2time_keeper.front()) { - client_coms_channel.thread_local_time = res->time_tick; - if(res->task.valid()) { - pending_tasks.emplace(client_coms_channel.my_id, res->time_tick, std::move(res->task)); - } - client_coms_channel.client2time_keeper.pop(); - } - min_local_time = std::min(client_coms_channel.thread_local_time, min_local_time); -#ifdef DEBUG_MT_SCHDULING - SCCTRACEALL("global_time_keeper::sync_local_times") - << "thread_local_time=" << sc_core::sc_time::from_value(client_coms_channel.thread_local_time); -#endif - } - this->all_threads_blocked.store(all_threads_blocked); - window_min_time = min_local_time; - sc_coms_channel.time_keeper2client.store(min_local_time); - auto window_max_time = min_local_time + std::max(tlm::tlm_global_quantum::instance().get().value(), sc_time_step.value()); - for(auto& client_coms_channel : client_coms_channels) { - client_coms_channel.time_keeper2client.store(window_max_time); - } -#ifdef DEBUG_MT_SCHDULING - SCCTRACEALL("global_time_keeper::sync_local_times") - << "sc_freerunning=" << all_threads_blocked << ", window_min_time=" << window_min_time; -#endif - } else if(stop_it.load()) { - break; - } - } -} -/////////////////////////////////////////////////////////////////////////////// -// -/////////////////////////////////////////////////////////////////////////////// -sc_time_syncronizer::sc_time_syncronizer(global_time_keeper& gtk) -: sc_core::sc_object("sc_time_syncronizer") -, gtk(gtk) { - sc_core::sc_register_stage_callback(*this, sc_core::SC_PRE_TIMESTEP | sc_core::SC_POST_END_OF_ELABORATION); - sc_core::sc_spawn_options opt; - opt.spawn_method(); - sc_core::sc_spawn([this]() { method_callback(); }, nullptr, &opt); -} - -void sc_time_syncronizer::method_callback() { - auto res = this->gtk.get_max_sc_time_ticks(); - if(res != std::numeric_limits::max()) { - sc_max_time.store(res, std::memory_order_seq_cst); - } - auto& pending_tasks = this->gtk.pending_tasks; - if(pending_tasks.size()) { -#ifdef DEBUG_MT_SCHDULING - SCCTRACEALL(__PRETTY_FUNCTION__) << "updating pending tasks"; -#endif - while(pending_tasks.size()) { - auto res = pending_tasks.front(); - auto& peq = sc_task_que[std::get<0>(*res)]; - auto t = std::get<1>(*res); -#ifdef DEBUG_MT_SCHDULING - SCCDEBUG(__PRETTY_FUNCTION__) << "scheduling task at " << t; -#endif - if(t > sc_core::sc_time_stamp().value()) - peq.notify(std::move(std::get<2>(*res)), sc_core::sc_time::from_value(t) - sc_core::sc_time_stamp()); - else - peq.notify(std::move(std::get<2>(*res))); -#ifdef DEBUG_MT_SCHDULING - SCCTRACEALL(__PRETTY_FUNCTION__) << "setting thread_blocked[" << std::get<0>(*res) << "]=1"; -#endif - pending_tasks.pop(); - } - sc_core::next_trigger(sc_core::SC_ZERO_TIME); - } else if(sc_core::sc_get_curr_simcontext()->pending_activity_at_current_time()) { -#ifdef DEBUG_MT_SCHDULING - SCCTRACEALL(__PRETTY_FUNCTION__) << "yield to next delta cycle"; -#endif - sc_core::next_trigger(sc_core::SC_ZERO_TIME); - } else { - auto time_to_next_evt = sc_core::sc_time_to_pending_activity(sc_core::sc_get_curr_simcontext()); - auto min_time = get_min_time(); - if(!sc_is_free_running) { - auto abs_time_to_next_evt = time_to_next_evt + sc_core::sc_time_stamp(); - if(min_time < abs_time_to_next_evt) { - if(min_time > sc_core::sc_time_stamp()) { - sc_core::next_trigger(min_time - sc_core::sc_time_stamp()); - } else { - // slow down systemc execution to be the slower than client threads - std::this_thread::yield(); - // std::this_thread::sleep_for(std::chrono::microseconds{1}); - sc_core::next_trigger(sc_core::SC_ZERO_TIME); // play it again, Sam - } - } else { -#ifdef DEBUG_MT_SCHDULING - SCCTRACEALL(__PRETTY_FUNCTION__) << "advancing SC time lockstepped to " << time_to_next_evt; -#endif - sc_core::next_trigger(time_to_next_evt); - } - } else { - // all threads are blocked by SystemC, so we can run freely but to avoid starving the kernel we only proceed to the - // time of the slowest client thread as this one is waiting to be served - auto next_time_point = sc_core::sc_time_stamp() + time_to_next_evt; - if(next_time_point.value() == std::numeric_limits::max()) { - time_to_next_evt = tlm_utils::tlm_quantumkeeper::get_global_quantum(); - if(time_to_next_evt == sc_core::SC_ZERO_TIME) - time_to_next_evt = 1_us; - } - sc_core::next_trigger(time_to_next_evt); - } - } -} - -void sc_time_syncronizer::stage_callback(const sc_core::sc_stage& stage) { - switch(stage) { - case sc_core::SC_PRE_TIMESTEP: { - sc_core::sc_time next_time; - if(sc_core::sc_get_curr_simcontext()->next_time(next_time)) { - gtk.update_sc_time_ticks(next_time.value()); - } -#ifdef DEBUG_MT_SCHDULING - SCCTRACEALL(SCMOD) << "advancing SystemC kernel time to " << next_time << ", get_min_time()=" << get_min_time(); -#endif - } break; - case sc_core::SC_POST_END_OF_ELABORATION: - gtk.start(); - break; - default: - break; - } -} - -} // namespace scc -} // namespace tlm \ No newline at end of file diff --git a/src/sysc/tlm/scc/quantum_keeper.h b/src/sysc/tlm/scc/quantum_keeper.h index 57eeacb4b..284291e54 100644 --- a/src/sysc/tlm/scc/quantum_keeper.h +++ b/src/sysc/tlm/scc/quantum_keeper.h @@ -1,429 +1,22 @@ #ifndef __SCC_TLM_QUANTUMKEEPER_H__ #define __SCC_TLM_QUANTUMKEEPER_H__ -#include "rigtorp/SPSCQueue.h" -#include "sysc/kernel/sc_wait.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - +#include "quantum_keeper_st.h" #if SC_VERSION_MAJOR < 3 -#error "Multithreaded quantum keeper is only supported with SystemC 3.0 and newer" -#else -// #define DEBUG_MT_SCHDULING - +#warning "Multithreaded quantum keeper is only supported with SystemC 3.0 and newer" namespace tlm { namespace scc { -#ifdef __cpp_lib_hardware_interference_size -static constexpr size_t kCacheLineSize = std::hardware_destructive_interference_size; +using quantumkeeper_mt = quantumkeeper_st; +} // namespace scc +} // namespace tlm #else -static constexpr size_t kCacheLineSize = 64; +#include "quantum_keeper_mt.h" +#define DEBUG_MT_SCHEDULING #endif -/////////////////////////////////////////////////////////////////////////////// -// -/////////////////////////////////////////////////////////////////////////////// -struct quantumkeeper : public tlm_utils::tlm_quantumkeeper { - using base = tlm_utils::tlm_quantumkeeper; - - quantumkeeper() {} - - virtual ~quantumkeeper() {} - - inline void check_and_sync(sc_core::sc_time core_inc) { - // devirtualized inc() - m_local_time += core_inc; - if((sc_core::sc_time_stamp() + m_local_time) >= m_next_sync_point) { - // devirtualized sync() - ::sc_core::wait(m_local_time); - m_local_time = sc_core::SC_ZERO_TIME; - m_next_sync_point = sc_core::sc_time_stamp() + compute_local_quantum(); - } - } - sc_core::sc_time get_local_absolute_time() const { return base::get_current_time(); } - /** - * @brief execute the given function in the SystemC thread - * - * @param fct the function to execute - */ - inline void execute_on_sysc(std::function fct) { execute_on_sysc(fct, sc_core::sc_time_stamp()); } - /** - * @brief execute the given function in the SystemC thread at a given point in time - * - * @param fct the function to execute - * @param when the time at which simulation time to execute the function in absolute time ticks - */ - inline void execute_on_sysc(std::function& fct, sc_core::sc_time when) { - if(when > sc_core::SC_ZERO_TIME) - wait(when); - fct(); - } -}; -/////////////////////////////////////////////////////////////////////////////// -// -/////////////////////////////////////////////////////////////////////////////// -using callback_fct = sc_core::sc_time(void); -using callback_task = std::packaged_task; - -struct comms_entry { - uint64_t time_tick; - callback_task task; -}; -/////////////////////////////////////////////////////////////////////////////// -// -/////////////////////////////////////////////////////////////////////////////// -/** - * @brief the communication channel between client threads (incl. SystemC) and the - * global time keeper - * - */ -struct thread_comms_channel { - /** - * @brief Construct a new thread comms channel object - * - * @param my_id - */ - thread_comms_channel(uint64_t my_id) - : my_id(my_id) - , client2time_keeper(7) - , time_keeper2client(0) {} - /** - * @brief Construct a new thread comms channel object - * - * @param o - */ - thread_comms_channel(thread_comms_channel const& o) - : my_id(o.my_id) - , client2time_keeper(o.client2time_keeper.capacity()) - , time_keeper2client(0) - , thread_local_time(o.thread_local_time) { - assert(o.client2time_keeper.size() == 0); - }; - - rigtorp::SPSCQueue client2time_keeper; - std::atomic time_keeper2client; - - const uint64_t my_id; - uint64_t thread_local_time; -}; -/////////////////////////////////////////////////////////////////////////////// -// -/////////////////////////////////////////////////////////////////////////////// -struct sc_time_syncronizer; -/** - * @brief the global time keeper, a singleton - * @details The global time keeper receives actual time stamps of all client threads - * and calculates the max time they are alloed to advance beyond systemc. All calculations are done - * based on ticks which is the minimal time step a simulator can do. The SystemC thread is special - * in that it is time wise always the last/slowest thread. - * - */ -struct global_time_keeper { - friend class sc_time_syncronizer; - /** - * @brief the maximum timestep the simulator is allowed to do if there are no future events - * - */ - const sc_core::sc_time sc_time_step = 1_ms; - /** - * @brief the singleton getter - * - * @return global_time_keeper& - */ - static global_time_keeper& get() { - static global_time_keeper keeper; - return keeper; - } - /** - * @brief Get the minimum time ticks of all client threads (not SystemC) - * - * @return uint64_t the absolute number of ticks - */ - inline uint64_t get_min_time_ticks() { return window_min_time; } - /** - * @brief Get the maximum time ticks a client thread is allowed to advance - * - * @param idx the id of the client thread, to be obtained using get_channel_index() - * @return uint64_t the absolute number of maximum ticks, if no new information it returns -1 - */ - inline uint64_t get_max_time_ticks(size_t idx) { return client_coms_channels[idx].time_keeper2client.load(); } - /** - * @brief updates the global time keeper with the local time ticks of a clinet thread - * - * @param idx the id of the client thread, to be obtained using get_channel_index() - * @param tick the absolute number of actual ticks - */ - inline void update_time_ticks(size_t idx, uint64_t tick) { - client_coms_channels[idx].client2time_keeper.push(std::move(comms_entry{tick, std::move(callback_task())})); - update_it.store(true); - std::unique_lock lk(upd_mtx); - update.notify_all(); - } - /** - * @brief updates the global time keeper with the local time ticks of a clinet thread and schedules a task at this new time point - * - * @param idx the id of the client thread, to be obtained using get_channel_index() - * @param tick the absolute number of actual ticks - * @param task the task to be executed in the SystemC kernel. It shall return the time it used for execution - */ - inline void schedule_task(size_t idx, std::packaged_task&& task, uint64_t when) { - client_coms_channels[idx].client2time_keeper.push(std::move(comms_entry{when, std::move(task)})); - update_it.store(true); - std::unique_lock lk(upd_mtx); - update.notify_all(); - } - /** - * @brief Get the maximum sc time ticks a client thread is allowed to advance - * - * @return uint64_t the absolute number of ticks - */ - inline uint64_t get_max_sc_time_ticks() { return sc_coms_channel.time_keeper2client.load(); } - /** - * @brief updates the global time keeper with the local time ticks of the SystemC thread - * - * @param tick the absolute number of actual SystemC ticks - */ - inline void update_sc_time_ticks(uint64_t tick) { - sc_coms_channel.client2time_keeper.push(std::move(comms_entry{tick, std::move(callback_task())})); -#ifdef DEBUG_MT_SCHDULING - SCCTRACEALL("global_time_keeper::update_sc_time_ticks") << "sc_time=" << sc_core::sc_time::from_value(tick); -#endif - update_it.store(true); - std::unique_lock lk(upd_mtx); - update.notify_all(); - } - -protected: - global_time_keeper(); - - global_time_keeper(global_time_keeper const&) = delete; - - global_time_keeper(global_time_keeper&&) = delete; - - ~global_time_keeper(); - - void start(); - - size_t get_channel_index(); - - void sync_local_times(); - - std::atomic stop_it{false}; - std::atomic update_it{false}; - std::atomic_bool all_threads_blocked{true}; - std::atomic_uint64_t window_min_time; - std::mutex upd_mtx; - std::condition_variable update; - thread_comms_channel sc_coms_channel{0}; - std::deque client_coms_channels; - rigtorp::SPSCQueue> pending_tasks{1024}; - bool started = false; -}; -/////////////////////////////////////////////////////////////////////////////// -// -/////////////////////////////////////////////////////////////////////////////// -/** - *the SystemC time keeper, a singleton in the SystemC thread - */ -struct sc_time_syncronizer : sc_core::sc_object, sc_core::sc_stage_callback_if, sc_core::sc_process_host { - ~sc_time_syncronizer() = default; - /** - * @brief the singleton getter - * - * @return sc_time_syncronizer& - */ - static sc_time_syncronizer& get() { - static sc_time_syncronizer keeper(global_time_keeper::get()); - return keeper; - } - /** - * @brief Get the channel index - * - * @details it creates all needed infrastructure to handle the data exchange between client threads and the SystemC thread - * - * @return size_t the index - */ - size_t get_channel_index() { - auto res = gtk.get_channel_index(); - sc_task_que.emplace_back(); - blocked_channels.emplace_back(true); - auto& sctq = sc_task_que[sc_task_que.size() - 1]; - sc_core::sc_spawn( - [this, &sctq]() { - wait(sc_core::SC_ZERO_TIME); - while(true) { - sctq.get()(); - } - }, - sc_core::sc_gen_unique_name("peq_cb", false)); - - return res; - } - /** - * @brief Get the current sc kernel time - * - * @return sc_core::sc_time - */ - inline sc_core::sc_time get_sc_kernel_time() { return sc_core::sc_time::from_value(sc_max_time.load(std::memory_order_seq_cst)); } - - inline void notify_channel_blocked(size_t idx, bool is_blocked) { - assert(idx < blocked_channels.size()); - blocked_channels[idx] = is_blocked; - sc_is_free_running = !std::any_of(std::begin(blocked_channels), std::end(blocked_channels), [](bool b) { return !b; }); - } - -private: - sc_time_syncronizer(global_time_keeper& gtk); - void method_callback(); - void stage_callback(const sc_core::sc_stage& stage) override; - sc_core::sc_time get_min_time() { return sc_core::sc_time::from_value(gtk.get_min_time_ticks()); } - global_time_keeper& gtk; - sc_core::sc_vector<::scc::peq> sc_task_que; - std::atomic_int64_t sc_max_time{0}; - sc_core::sc_process_handle method_handle; - std::vector blocked_channels; - bool sc_is_free_running{true}; -}; -/////////////////////////////////////////////////////////////////////////////// -// -/////////////////////////////////////////////////////////////////////////////// -/** - * the multi threading quantum keeper - */ -struct quantumkeeper_mt { - /** - * @brief Construct a new quantumkeeper mt object - * - */ - quantumkeeper_mt() - : gtk_idx(sc_time_syncronizer::get().get_channel_index()) { - sc_core::sc_spawn_options opt; - opt.spawn_method(); - opt.set_sensitivity(&keep_alive); - } - /** - * @brief Destroy the quantumkeeper mt object - * - */ - virtual ~quantumkeeper_mt() = default; - - void run_thread(std::function f) { - sc_time_syncronizer::get().notify_channel_blocked(gtk_idx, false); - thread_executor.start(f); - wait(thread_executor.thread_finish_event()); - } - /** - * @brief increments the local time and updates the global time keeper with the new time - * - * @param core_inc the amount to increment the local time - */ - inline void inc(sc_core::sc_time const& core_inc) { - local_absolute_time += core_inc; - auto res = global_time_keeper::get().get_max_time_ticks(gtk_idx); - if(res != std::numeric_limits::max()) - local_time_ticks_limit = res; - } - /** - * @brief checks the local time against the global time keeper and holds the thread until it is within the quantum - * - * @param core_inc the amount to increment the local time - */ - inline void check_and_sync(sc_core::sc_time const& core_inc) { - inc(core_inc); - global_time_keeper::get().update_time_ticks(gtk_idx, local_absolute_time.value()); - // wait until the SystemC thread advanced to the same time then we are minus the global quantum - while(local_absolute_time.value() > local_time_ticks_limit) { - std::this_thread::yield(); // should do the same than __builtin_ia32_pause() or _mm_pause() on MSVC - auto res = global_time_keeper::get().get_max_time_ticks(gtk_idx); - if(res != std::numeric_limits::max()) - local_time_ticks_limit = res; - } - } - /** - * @brief execute the given function in the SystemC thread - * - * @param fct the function to execute - */ - inline void execute_on_sysc(std::function fct) { execute_on_sysc(fct, local_absolute_time); } - /** - * @brief execute the given function in the SystemC thread at a given point in time - * - * @param fct the function to execute - * @param when the time at which simulation time to execute the function in absolute time ticks - */ - inline void execute_on_sysc(std::function& fct, sc_core::sc_time when) { - callback_task task([this, &fct]() { - sc_time_syncronizer::get().notify_channel_blocked(gtk_idx, true); - auto t0 = sc_core::sc_time_stamp(); - fct(); - sc_time_syncronizer::get().notify_channel_blocked(gtk_idx, false); - return t0 - sc_core::sc_time_stamp(); - }); - auto ret = task.get_future(); - global_time_keeper::get().schedule_task(gtk_idx, std::move(task), when.value()); - auto duration = ret.get(); - check_and_sync(duration); - } - /** - * @brief Get the local time of this thread - * - * @return sc_core::sc_time the local time relative to the SystemC kernel - */ - sc_core::sc_time get_local_time() const { return local_absolute_time - get_current_sc_time(); } - /** - * @brief Get the local absolute time of this thread - * - * @return sc_core::sc_time the local time - */ - sc_core::sc_time get_local_absolute_time() const { return local_absolute_time; } - /** - * @brief Get the current sc time of this thread - * - * @return sc_core::sc_time the time of the SystemC kernel - */ - sc_core::sc_time get_current_sc_time() const { return sc_time_syncronizer::get().get_sc_kernel_time(); } - /** - * @brief resets the local time of this thread to the SystemC kernel time - * - */ - void reset() { reset(get_current_sc_time()); } - /** - * @brief esets the local time of this thread to the given absolute time - * - * @param abs_time the absolute time to set - */ - void reset(sc_core::sc_time abs_time) { local_absolute_time = sc_core::sc_time_stamp(); } - - void unblock_thread() { global_time_keeper::get().update_time_ticks(gtk_idx, local_absolute_time.value()); } - -protected: - size_t gtk_idx; - ::scc::async_thread thread_executor; - sc_core::sc_event keep_alive; - sc_core::sc_time local_absolute_time; - uint64_t local_time_ticks_limit{0}; -}; +namespace tlm { +namespace scc { +using quantumkeeper = quantumkeeper_st; } // namespace scc } // namespace tlm -#endif + #endif diff --git a/src/sysc/tlm/scc/quantum_keeper_mt.h b/src/sysc/tlm/scc/quantum_keeper_mt.h new file mode 100644 index 000000000..0bbdb4064 --- /dev/null +++ b/src/sysc/tlm/scc/quantum_keeper_mt.h @@ -0,0 +1,136 @@ +#ifndef __SCC_TLM_QUANTUMKEEPER_MT_H__ +#define __SCC_TLM_QUANTUMKEEPER_MT_H__ + +#include "qk/sc_time_syncronizer.h" +#include + +#if SC_VERSION_MAJOR < 3 +#error "Multithreaded quantum keeper is only supported with SystemC 3.0 and newer" +#else +#define DEBUG_MT_SCHEDULING + +namespace tlm { +namespace scc { +/** + * the multi threading quantum keeper + */ +struct quantumkeeper_mt { + /** + * @brief Construct a new quantumkeeper mt object + * + */ + quantumkeeper_mt() + : gtk_idx(qk::sc_time_syncronizer::get().get_channel_index()) { + sc_core::sc_spawn_options opt; + opt.spawn_method(); + opt.set_sensitivity(&keep_alive); + } + /** + * @brief Destroy the quantumkeeper mt object + * + */ + virtual ~quantumkeeper_mt() = default; + + void run_thread(std::function f) { + qk::sc_time_syncronizer::get().notify_client_blocked(gtk_idx, false); + thread_executor.start(f); + wait(thread_executor.thread_finish_event()); + } + /** + * @brief increments the local time and updates the global time keeper with the new time + * + * @param core_inc the amount to increment the local time + */ + inline void inc(sc_core::sc_time const& core_inc) { + local_absolute_time += core_inc; + auto res = qk::global_time_keeper::get().get_max_time_ticks(gtk_idx); + if(res != std::numeric_limits::max()) + local_time_ticks_limit = res; + } + /** + * @brief checks the local time against the global time keeper and holds the thread until it is within the quantum + * + * @param core_inc the amount to increment the local time + */ + inline void check_and_sync(sc_core::sc_time const& core_inc) { + inc(core_inc); + qk::global_time_keeper::get().update_time_ticks(gtk_idx, local_absolute_time.value()); + // wait until the SystemC thread advanced to the same time then we are minus the global quantum + while(local_absolute_time.value() > local_time_ticks_limit) { + std::this_thread::yield(); // should do the same than __builtin_ia32_pause() or _mm_pause() on MSVC + auto res = qk::global_time_keeper::get().get_max_time_ticks(gtk_idx); + if(res != std::numeric_limits::max()) + local_time_ticks_limit = res; + } + } + /** + * @brief execute the given function in the SystemC thread + * + * @param fct the function to execute + */ + inline void execute_on_sysc(std::function fct) { execute_on_sysc(fct, local_absolute_time); } + /** + * @brief execute the given function in the SystemC thread at a given point in time + * + * @param fct the function to execute + * @param when the time at which simulation time to execute the function in absolute time ticks + */ + inline void execute_on_sysc(std::function& fct, sc_core::sc_time when) { + qk::callback_task task([this, &fct]() { + auto t0 = sc_core::sc_time_stamp(); + qk::sc_time_syncronizer::get().notify_client_blocked(gtk_idx, true); + fct(); + qk::sc_time_syncronizer::get().notify_client_blocked(gtk_idx, false); + return sc_core::sc_time_stamp() - t0; + }); + auto ret = task.get_future(); + qk::global_time_keeper::get().schedule_task(gtk_idx, std::move(task), when.value()); + auto duration = ret.get(); +#ifdef DEBUG_MT_SCHEDULING + SCCTRACEALL("quantumkeeper_mt") << "execute_on_sysc took " << duration; +#endif + check_and_sync(duration); + } + /** + * @brief Get the local time of this thread + * + * @return sc_core::sc_time the local time relative to the SystemC kernel + */ + sc_core::sc_time get_local_time() const { return local_absolute_time - get_current_sc_time(); } + /** + * @brief Get the local absolute time of this thread + * + * @return sc_core::sc_time the local time + */ + sc_core::sc_time get_local_absolute_time() const { return local_absolute_time; } + /** + * @brief Get the current sc time of this thread + * + * @return sc_core::sc_time the time of the SystemC kernel + */ + sc_core::sc_time get_current_sc_time() const { return qk::sc_time_syncronizer::get().get_sc_kernel_time(); } + /** + * @brief resets the local time of this thread to the SystemC kernel time + * + */ + void reset() { reset(get_current_sc_time()); } + /** + * @brief esets the local time of this thread to the given absolute time + * + * @param abs_time the absolute time to set + */ + void reset(sc_core::sc_time const& abs_time) { local_absolute_time = sc_core::sc_time_stamp(); } + + void unblock_thread() { qk::global_time_keeper::get().update_time_ticks(gtk_idx, local_absolute_time.value()); } + +protected: + size_t gtk_idx; + ::scc::async_thread thread_executor; + sc_core::sc_event keep_alive; + sc_core::sc_time local_absolute_time; + uint64_t local_time_ticks_limit{0}; +}; +} // namespace scc +} // namespace tlm +#endif +#endif // __SCC_TLM_QUANTUMKEEPER_MT_H__ diff --git a/src/sysc/tlm/scc/quantum_keeper_st.h b/src/sysc/tlm/scc/quantum_keeper_st.h new file mode 100644 index 000000000..d39f95973 --- /dev/null +++ b/src/sysc/tlm/scc/quantum_keeper_st.h @@ -0,0 +1,57 @@ +#ifndef __SCC_TLM_QUANTUMKEEPER_ST_H__ +#define __SCC_TLM_QUANTUMKEEPER_ST_H__ + +#include +#include + +namespace tlm { +namespace scc { +/////////////////////////////////////////////////////////////////////////////// +// +/////////////////////////////////////////////////////////////////////////////// +struct quantumkeeper_st : public tlm_utils::tlm_quantumkeeper { + using base = tlm_utils::tlm_quantumkeeper; + + quantumkeeper_st() {} + + virtual ~quantumkeeper_st() {} + + void reset() override { tlm_utils::tlm_quantumkeeper::reset(); } + + inline void reset(sc_core::sc_time const& t) { + m_local_time = t - sc_core::sc_time_stamp(); + m_next_sync_point = t + compute_local_quantum(); + } + + inline void check_and_sync(sc_core::sc_time core_inc) { + // devirtualized inc() + m_local_time += core_inc; + if((sc_core::sc_time_stamp() + m_local_time) >= m_next_sync_point) { + // devirtualized sync() + ::sc_core::wait(m_local_time); + m_local_time = sc_core::SC_ZERO_TIME; + m_next_sync_point = sc_core::sc_time_stamp() + compute_local_quantum(); + } + } + sc_core::sc_time get_local_absolute_time() const { return base::get_current_time(); } + /** + * @brief execute the given function in the SystemC thread + * + * @param fct the function to execute + */ + inline void execute_on_sysc(std::function fct) { execute_on_sysc(fct, sc_core::sc_time_stamp()); } + /** + * @brief execute the given function in the SystemC thread at a given point in time + * + * @param fct the function to execute + * @param when the time at which simulation time to execute the function in absolute time ticks + */ + inline void execute_on_sysc(std::function& fct, sc_core::sc_time when) { + if(when > sc_core::SC_ZERO_TIME) + wait(when); + fct(); + } +}; +} // namespace scc +} // namespace tlm +#endif // __SCC_TLM_QUANTUMKEEPER_ST_H__ diff --git a/third_party/lwtr4sc b/third_party/lwtr4sc index e62781c95..ed77d959b 160000 --- a/third_party/lwtr4sc +++ b/third_party/lwtr4sc @@ -1 +1 @@ -Subproject commit e62781c95f7888668791c287e2b8bb19c24bad71 +Subproject commit ed77d959b8e9cc32ba88c392609d75fcbe4c6365