Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/common/util/defer.h
Original file line number Diff line number Diff line change
@@ -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 <class Lambda> 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 <class Lambda> constexpr CallOnScopeExit<Lambda> operator->*(const TagClassForLambda&, Lambda lambda) {
return CallOnScopeExit<Lambda>(lambda);
}
} // namespace Util
#endif // UTIL_DEFER_H
5 changes: 4 additions & 1 deletion src/sysc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
94 changes: 94 additions & 0 deletions src/sysc/tlm/scc/qk/global_time_keeper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include "global_time_keeper.h"
#include <tlm_utils/tlm_quantumkeeper.h>

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<std::mutex> 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<uint64_t>::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
125 changes: 125 additions & 0 deletions src/sysc/tlm/scc/qk/global_time_keeper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#ifndef __SCC_TLM_QK_GLOBAL_TIME_KEEPER_H__
#define __SCC_TLM_QK_GLOBAL_TIME_KEEPER_H__

#include "types.h"
#include <deque>

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<std::mutex> 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<sc_core::sc_time(void)>&& 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<std::mutex> 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<std::mutex> 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<bool> stop_it{false};
std::atomic<bool> 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<thread_comms_channel> client_coms_channels;
rigtorp::SPSCQueue<std::tuple<size_t, uint64_t, callback_task>> pending_tasks{1024};
bool started = false;
};
} // namespace qk
} // namespace scc
} // namespace tlm
#endif // __SCC_TLM_QK_GLOBAL_TIME_KEEPER_H__
Loading