scc 2025.09
SystemC components library
quantum_keeper.h
1#ifndef __SCC_TLM_QUANTUMKEEPER_H__
2#define __SCC_TLM_QUANTUMKEEPER_H__
3
4#include "rigtorp/SPSCQueue.h"
5#include "sysc/kernel/sc_wait.h"
6#include <atomic>
7#include <condition_variable>
8#include <cstddef>
9#include <cstdint>
10#include <deque>
11#include <future>
12#include <limits>
13#include <mutex>
14#include <nonstd/optional.hpp>
15#include <scc/async_thread.h>
16#include <scc/peq.h>
17#include <scc/report.h>
18#include <scc/utilities.h>
19#include <string>
20#include <sysc/kernel/sc_object.h>
21#include <sysc/kernel/sc_process.h>
22#include <sysc/kernel/sc_simcontext.h>
23#include <sysc/kernel/sc_spawn.h>
24#include <sysc/kernel/sc_spawn_options.h>
25#include <sysc/kernel/sc_time.h>
26#include <systemc>
27#include <thread>
28#include <tlm>
29#include <tlm_utils/tlm_quantumkeeper.h>
30
31#if SC_VERSION_MAJOR < 3
32#error "Multithreaded quantum keeper is only supported with SystemC 3.0 and newer"
33#else
34// #define DEBUG_MT_SCHDULING
35
36namespace tlm {
37namespace scc {
38#ifdef __cpp_lib_hardware_interference_size
39static constexpr size_t kCacheLineSize = std::hardware_destructive_interference_size;
40#else
41static constexpr size_t kCacheLineSize = 64;
42#endif
44//
46struct quantumkeeper : public tlm_utils::tlm_quantumkeeper {
47 using base = tlm_utils::tlm_quantumkeeper;
48
49 quantumkeeper() {}
50
51 virtual ~quantumkeeper() {}
52
53 inline void check_and_sync(sc_core::sc_time core_inc) {
54 // devirtualized inc()
55 m_local_time += core_inc;
56 if((sc_core::sc_time_stamp() + m_local_time) >= m_next_sync_point) {
57 // devirtualized sync()
58 ::sc_core::wait(m_local_time);
59 m_local_time = sc_core::SC_ZERO_TIME;
60 m_next_sync_point = sc_core::sc_time_stamp() + compute_local_quantum();
61 }
62 }
63 sc_core::sc_time get_local_absolute_time() const { return base::get_current_time(); }
69 inline void execute_on_sysc(std::function<void(void)> fct) { execute_on_sysc(fct, sc_core::sc_time_stamp()); }
76 inline void execute_on_sysc(std::function<void(void)>& fct, sc_core::sc_time when) {
77 if(when > sc_core::SC_ZERO_TIME)
78 wait(when);
79 fct();
80 }
81};
83//
85using callback_fct = sc_core::sc_time(void);
86using callback_task = std::packaged_task<callback_fct>;
87
88struct comms_entry {
89 uint64_t time_tick;
90 callback_task task;
91};
93//
95
100struct thread_comms_channel {
106 thread_comms_channel(uint64_t my_id)
107 : my_id(my_id)
108 , client2time_keeper(7)
109 , time_keeper2client(0) {}
115 thread_comms_channel(thread_comms_channel const& o)
116 : my_id(o.my_id)
117 , client2time_keeper(o.client2time_keeper.capacity())
118 , time_keeper2client(0)
119 , thread_local_time(o.thread_local_time) {
120 assert(o.client2time_keeper.size() == 0);
121 };
122
123 rigtorp::SPSCQueue<comms_entry> client2time_keeper;
124 std::atomic<uint64_t> time_keeper2client;
125
126 const uint64_t my_id;
127 uint64_t thread_local_time;
128};
130//
132struct sc_time_syncronizer;
141struct global_time_keeper {
142 friend class sc_time_syncronizer;
147 const sc_core::sc_time sc_time_step = 1_ms;
153 static global_time_keeper& get() {
154 static global_time_keeper keeper;
155 return keeper;
156 }
162 inline uint64_t get_min_time_ticks() { return window_min_time; }
169 inline uint64_t get_max_time_ticks(size_t idx) { return client_coms_channels[idx].time_keeper2client.load(); }
176 inline void update_time_ticks(size_t idx, uint64_t tick) {
177 client_coms_channels[idx].client2time_keeper.push(std::move(comms_entry{tick, std::move(callback_task())}));
178 update_it.store(true);
179 std::unique_lock<std::mutex> lk(upd_mtx);
180 update.notify_all();
181 }
189 inline void schedule_task(size_t idx, std::packaged_task<sc_core::sc_time(void)>&& task, uint64_t when) {
190 client_coms_channels[idx].client2time_keeper.push(std::move(comms_entry{when, std::move(task)}));
191 update_it.store(true);
192 std::unique_lock<std::mutex> lk(upd_mtx);
193 update.notify_all();
194 }
200 inline uint64_t get_max_sc_time_ticks() { return sc_coms_channel.time_keeper2client.load(); }
206 inline void update_sc_time_ticks(uint64_t tick) {
207 sc_coms_channel.client2time_keeper.push(std::move(comms_entry{tick, std::move(callback_task())}));
208#ifdef DEBUG_MT_SCHDULING
209 SCCTRACEALL("global_time_keeper::update_sc_time_ticks") << "sc_time=" << sc_core::sc_time::from_value(tick);
210#endif
211 update_it.store(true);
212 std::unique_lock<std::mutex> lk(upd_mtx);
213 update.notify_all();
214 }
215
216protected:
217 global_time_keeper();
218
219 global_time_keeper(global_time_keeper const&) = delete;
220
221 global_time_keeper(global_time_keeper&&) = delete;
222
223 ~global_time_keeper();
224
225 void start();
226
227 size_t get_channel_index();
228
229 void sync_local_times();
230
231 std::atomic<bool> stop_it{false};
232 std::atomic<bool> update_it{false};
233 std::atomic_bool all_threads_blocked{true};
234 std::atomic_uint64_t window_min_time;
235 std::mutex upd_mtx;
236 std::condition_variable update;
237 thread_comms_channel sc_coms_channel{0};
238 std::deque<thread_comms_channel> client_coms_channels;
239 rigtorp::SPSCQueue<std::tuple<size_t, uint64_t, callback_task>> pending_tasks{1024};
240 bool started = false;
241};
243//
245
248struct sc_time_syncronizer : sc_core::sc_object, sc_core::sc_stage_callback_if, sc_core::sc_process_host {
249 ~sc_time_syncronizer() = default;
255 static sc_time_syncronizer& get() {
256 static sc_time_syncronizer keeper(global_time_keeper::get());
257 return keeper;
258 }
266 size_t get_channel_index() {
267 auto res = gtk.get_channel_index();
268 sc_task_que.emplace_back();
269 blocked_channels.emplace_back(true);
270 auto& sctq = sc_task_que[sc_task_que.size() - 1];
271 sc_core::sc_spawn(
272 [this, &sctq]() {
273 wait(sc_core::SC_ZERO_TIME);
274 while(true) {
275 sctq.get()();
276 }
277 },
278 sc_core::sc_gen_unique_name("peq_cb", false));
279
280 return res;
281 }
287 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)); }
288
289 inline void notify_channel_blocked(size_t idx, bool is_blocked) {
290 assert(idx < blocked_channels.size());
291 blocked_channels[idx] = is_blocked;
292 sc_is_free_running = !std::any_of(std::begin(blocked_channels), std::end(blocked_channels), [](bool b) { return !b; });
293 }
294
295private:
296 sc_time_syncronizer(global_time_keeper& gtk);
297 void method_callback();
298 void stage_callback(const sc_core::sc_stage& stage) override;
299 sc_core::sc_time get_min_time() { return sc_core::sc_time::from_value(gtk.get_min_time_ticks()); }
300 global_time_keeper& gtk;
301 sc_core::sc_vector<::scc::peq<callback_task>> sc_task_que;
302 std::atomic_int64_t sc_max_time{0};
303 sc_core::sc_process_handle method_handle;
304 std::vector<bool> blocked_channels;
305 bool sc_is_free_running{true};
306};
308//
310
313struct quantumkeeper_mt {
318 quantumkeeper_mt()
319 : gtk_idx(sc_time_syncronizer::get().get_channel_index()) {
320 sc_core::sc_spawn_options opt;
321 opt.spawn_method();
322 opt.set_sensitivity(&keep_alive);
323 }
328 virtual ~quantumkeeper_mt() = default;
329
330 void run_thread(std::function<sc_core::sc_time()> f) {
331 sc_time_syncronizer::get().notify_channel_blocked(gtk_idx, false);
332 thread_executor.start(f);
333 wait(thread_executor.thread_finish_event());
334 }
340 inline void inc(sc_core::sc_time const& core_inc) {
341 local_absolute_time += core_inc;
342 auto res = global_time_keeper::get().get_max_time_ticks(gtk_idx);
343 if(res != std::numeric_limits<uint64_t>::max())
344 local_time_ticks_limit = res;
345 }
351 inline void check_and_sync(sc_core::sc_time const& core_inc) {
352 inc(core_inc);
353 global_time_keeper::get().update_time_ticks(gtk_idx, local_absolute_time.value());
354 // wait until the SystemC thread advanced to the same time then we are minus the global quantum
355 while(local_absolute_time.value() > local_time_ticks_limit) {
356 std::this_thread::yield(); // should do the same than __builtin_ia32_pause() or _mm_pause() on MSVC
357 auto res = global_time_keeper::get().get_max_time_ticks(gtk_idx);
358 if(res != std::numeric_limits<uint64_t>::max())
359 local_time_ticks_limit = res;
360 }
361 }
367 inline void execute_on_sysc(std::function<void(void)> fct) { execute_on_sysc(fct, local_absolute_time); }
374 inline void execute_on_sysc(std::function<void(void)>& fct, sc_core::sc_time when) {
375 callback_task task([this, &fct]() {
376 sc_time_syncronizer::get().notify_channel_blocked(gtk_idx, true);
377 auto t0 = sc_core::sc_time_stamp();
378 fct();
379 sc_time_syncronizer::get().notify_channel_blocked(gtk_idx, false);
380 return t0 - sc_core::sc_time_stamp();
381 });
382 auto ret = task.get_future();
383 global_time_keeper::get().schedule_task(gtk_idx, std::move(task), when.value());
384 auto duration = ret.get();
385 check_and_sync(duration);
386 }
392 sc_core::sc_time get_local_time() const { return local_absolute_time - get_current_sc_time(); }
398 sc_core::sc_time get_local_absolute_time() const { return local_absolute_time; }
404 sc_core::sc_time get_current_sc_time() const { return sc_time_syncronizer::get().get_sc_kernel_time(); }
409 void reset() { reset(get_current_sc_time()); }
415 void reset(sc_core::sc_time abs_time) { local_absolute_time = sc_core::sc_time_stamp(); }
416
417 void unblock_thread() { global_time_keeper::get().update_time_ticks(gtk_idx, local_absolute_time.value()); }
418
419protected:
420 size_t gtk_idx;
421 ::scc::async_thread thread_executor;
422 sc_core::sc_event keep_alive;
423 sc_core::sc_time local_absolute_time;
424 uint64_t local_time_ticks_limit{0};
425};
426} // namespace scc
427} // namespace tlm
428#endif
429#endif
SCC TLM utilities.
Definition axis_tlm.h:56
SystemC TLM.
Definition dmi_mgr.h:19