WIP: Parallel SystemC Module - #118
Draft
markfoodyburton wants to merge 52 commits into
Draft
Conversation
Contributor
Author
|
Todo: wrap calls to b_transport/nb_transport etc such that they are called on the right simcontext. |
maehne
reviewed
May 12, 2025
maehne
left a comment
Contributor
There was a problem hiding this comment.
So far, PR looks good. As you you mention WIP in title, I would suggest that you mark it explicitly as draft in the PR's properties.
| sc_time time; | ||
|
|
||
| void stage_callback(const sc_core::sc_stage& stage) { | ||
| mutex.lock(); |
markfoodyburton
force-pushed
the
mburton/psystemc
branch
2 times, most recently
from
May 15, 2025 16:03
97c6717 to
c2d4c18
Compare
markfoodyburton
marked this pull request as draft
May 15, 2025 16:05
Contributor
Author
|
Still an issue to do with prim_channels - created due to dynamic event creation after start of simulation, which generates a new 'name' - putting that into the name hierarchy is not thread safe, and causes things to go wrong if you run for long enough..... |
Contributor
Author
THANKS @maehne ! |
markfoodyburton
force-pushed
the
mburton/psystemc
branch
2 times, most recently
from
May 26, 2025 13:57
5feda24 to
69f610b
Compare
markfoodyburton
force-pushed
the
mburton/psystemc
branch
from
June 5, 2025 12:30
69f610b to
c60e62c
Compare
Signed-off-by: Mark Burton <mburton@quicinc.com>
Signed-off-by: Mark Burton <mburton@quicinc.com>
Signed-off-by: Mark Burton <mburton@quicinc.com>
Signed-off-by: Mark Burton <mburton@quicinc.com>
…he protected, remove SCMOD Signed-off-by: Mark Burton <mburton@quicinc.com>
Signed-off-by: Mark Burton <mburton@quicinc.com>
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
…, and document it in the example Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
Bug fixes: - Prefer fmt::format over std::format when fmt is available, fixing incompatibility with fmt types (e.g. fmt::join) on C++20 - Use SC_LOG_LOG_LEVEL_CACHE_GLOBAL in VBSTY_CHECK1 else branch to fix "invalid use of non-static data member" in nested classes - Fix VECTOR_PUSH_BACK field mapping bug (tag_str was placed in scname field due to incorrect aggregate init; now uses factory make()) - Fix empty string fallback in sc_logger: (t && *t) ? t : "SystemC" - Use .c_str() instead of .data() in GET_TAG macros Ownership and types: - Change tag and scname from string_view to std::string for clear ownership semantics. SSO avoids heap allocation for typical tags. Internal (no standard API impact): - sc_log_logger_cache::set_tag(std::string) — change tag, reset cache - sc_log_logger_cache::get_current()/set_current() — thread-safe accessor for current logger pointer across shared library boundaries. Set by get_log_verbosity_cached(), cleared by ~sc_logger(). - SC_LOG_LOG_LEVEL_CACHE_GLOBAL macro for nested class scoping Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
- CMake: detect std::format support at configure time; when not available, find_package(fmt) as fallback - Guard <format> / <fmt/format.h> includes with __has_include; prefer std::format (__cpp_lib_format), fall back to fmt (FMT_VERSION) - Add missing <functional>, <string>, <string_view> to sc_log_types.h (transitively included on macOS but not on Linux) Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
…n/sc_log Fix to sc_log vector push, enable c++17 support
Accoding to IEEE 2023 the width of rsults from of right shifts should be the same ss the input variable.
GCC8.5 does not ignore the false positive -Wshift-negative-value. Pragmas introduced in 7ff1521 do not work. Signed-off-by: Lukas Jünger <lukas@mwa.re>
Signed-off-by: Lukas Jünger <lukas@mwa.re>
Signed-off-by: Lukas Jünger <lukas@mwa.re>
Signed-off-by: Nils Bosbach <bosbach@ice.rwth-aachen.de>
Make sc_curr_simcontext / sc_default_global_context thread_local so each std::thread can have its own current simcontext, and make sc_process_b::m_last_created_process_p thread_local for the same reason — "last created process" is inherently a per-thread concept. Teach sc_simcontext::init() to recognise the case where it is being called for a "child" simcontext (sc_curr_simcontext set and != this). The child shares parent's sc_object_manager / sc_name_gen, inherits the parent's dynamic_log_verbosity callback, and records the parent in m_parent_context so clean() can skip the shared resources. Foundation for running multiple cooperating simcontexts on different std::threads. Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
Make the kernel-internal static state thread safe so that multiple
simcontexts running concurrently cannot corrupt shared data structures:
* sc_name_gen: add a std::mutex and take it around gen_unique_name
and the destructor.
* sc_object_manager: add a std::mutex and take it around
create_name, insert/remove_event, insert/remove_object,
insert/remove_external_name.
* sc_event_timed free_list: thread_local (per-thread allocator).
* sc_simcontext::sc_start's init_delta_or_pending_updates flag:
thread_local.
* sc_mempool: the_mempool becomes thread_local, use_default_new
becomes std::atomic<bool> (written by compute_use_default_new on
first allocation, read on every allocation).
* sc_prim_channel::async_update_list::attach_suspending /
detach_suspending: replace the misleading
"// return releases the mutex" trailing comments with a correct
block comment stating the same-thread-only invariant (every
caller is on the owning simcontext's thread by construction).
No behavioural change on a single-thread simulation. Foundation for
the cross-simcontext work in the following patches.
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
Introduce a per-simcontext sc_async_runnable_helper - a sc_prim_channel
that accepts work from foreign threads and drains it onto the owning
simcontext's update phase. Three entry points:
* post_method(sc_method_process*) / post_thread(sc_thread_process*)
fast-path runnable pushes for trigger fan-out. Drain dedups via
is_runnable() to keep the runnable list well-formed.
* post_callback(std::function<void()>) general-purpose cross-
context call routing.
On top of these, sc_simcontext gains three inline methods:
* push_runnable_method_async / push_runnable_thread_async - if
caller is on a foreign thread, post via the helper; else call
the direct push_runnable_* function.
* run_update_async(fn) - if foreign, post the callback; else run
inline.
Plus sc_prim_channel gains a protected forwarder
sc_prim_channel::run_update_async() that delegates to its owning
simcontext's method (reached via friendship with sc_simcontext).
This lets derived channels (e.g. sc_signal in a later patch) route
cross-context work without being friends of sc_simcontext themselves.
The helper is constructed in sc_simcontext::init() with the current
simcontext temporarily switched so it registers with this simcontext's
prim_channel_registry. The simcontext's state is SC_ELABORATION at
that point, so the regular sc_prim_channel() constructor's
sc_is_running()/elaboration_done() checks both pass.
No existing call site uses any of these yet; later patches wire them
into sc_event, the process trigger paths, sc_signal and the user
control structures.
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
Add a cross-context guard at every sc_event notify / cancel entry point: if the caller's sc_curr_simcontext differs from the event's owning m_simc, post a callback via run_update_async that re-invokes the same entry point on the owner's thread. The owner's update-phase drain then runs the body locally under the kernel's normal coalescing (m_notify_type in sc_event::notify(t)), so repeat cross-context notifies on the same event do NOT produce repeat kernel notifies. Entry points covered: sc_event::notify() sc_event::notify(const sc_time&) sc_event::cancel() sc_event::notify_delayed() sc_event::notify_delayed(const sc_time&) sc_event::notify_internal(const sc_time&) (inline in .h) sc_event::notify_next_delta() (inline in .h) Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
Two distinct cross-context shapes in the process classes need
routing onto the owning simcontext's thread:
1. Trigger fan-out: sc_event::trigger / trigger_dynamic walks the
event's static and dynamic subscriber lists and calls
trigger_static / trigger_dynamic on each subscriber process.
If the event fires on simcontext A but the subscriber lives in
simcontext B, the existing code path called
simcontext()->push_runnable_method(this) - on B's runnable list
from A's thread, with no synchronisation. Replace with the
async variant push_runnable_method_async / push_runnable_thread_async
introduced in the previous patch, which dispatches locally if
curr_simcontext matches and posts to the owner's helper otherwise.
Sites:
sc_method_process::trigger_static (header inline)
sc_method_process::trigger_dynamic
sc_thread_process::trigger_static (header inline)
sc_thread_process::trigger_dynamic
2. enable_process / resume_process: these read-modify-write m_state
and may call push_runnable_*. Both must happen on the owning
sim's thread. Add a guard at function entry: if foreign, post
the whole call via run_update_async and return. The body's
existing push_runnable_* calls then run unconditionally locally,
so they go straight to push_runnable_method/thread (no _async
needed inside the body).
Sites:
sc_method_process::enable_process
sc_method_process::resume_process
sc_thread_process::enable_process
sc_thread_process::resume_process
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
The fix at the trigger fan-out side covered the reader path: a signal written from its owning simcontext fires its value-changed event, whose fan-out wakes a foreign subscriber via push_runnable_method_async. The writer path was still racy. sc_signal::write mutates m_new_val directly on whichever thread called it and then invokes sc_prim_channel::request_update, which appends to a non-async update list of its owning simcontext. A foreign-thread write therefore both raced on m_new_val and corrupted the update list. Add a cross-context guard at the top of sc_signal_t<T,POL>::write: if the caller is on a foreign simcontext, post the write back through sc_prim_channel::run_update_async (added in the helper patch). The lambda captures value_ by copy and recurses via this->write(value_), which on the owner's thread takes the local branch and runs the existing body. Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
sc_report_handler's static state raced on every report path under parallel simcontexts: sev_call_count[] is incremented on every SC_REPORT_*; the handler pointer, suppress/force masks, verbosity, log file handle, log_file_name, and the message-def linked list are all written via the public set_* APIs and read on every report. Concurrent calls from threads belonging to different simcontexts could corrupt counters, the message linked list, or the log file's internal state. The mechanism must not crash on cross-thread use; the semantics of how things are observed are implementation-defined and left to the user-installed handler (the back-end) to coordinate. Make every mutable piece of sc_report_handler state thread_local: suppress_mask, force_mask, sev_actions[], sev_limit[], sev_call_count[], last_global_report, available_actions, catch_actions, log_file_name, verbosity_level, messages, handler, and the file-scope log_stream sc_log_file_handle. msg_terminator stays shared (read-only, compile-time initialised). Behavioural consequence: set_handler / set_verbosity_level / set_actions only affect the thread they are called from. To avoid the "configured in sc_main but child simcontext sees defaults" surprise, expose: sc_report_handler::config_snapshot snapshot_config(); void apply_config(const config_snapshot&); snapshot_config() captures the current thread's report config; a later patch (sc_concurrent) calls it on the parent before spawning and apply_config() on the child thread first thing. Counters are deliberately left out of the snapshot — a child thread starts with fresh sev_call_count. Users wanting unified cross-thread policy beyond the spawn-time inheritance install a thread-aware handler or rebuild the snapshot themselves; the back-end is where that coordination belongs. Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
markfoodyburton
force-pushed
the
mburton/psystemc
branch
2 times, most recently
from
May 12, 2026 14:07
a4022a8 to
7fb25b8
Compare
sc_on_context (new utility header) is a sc_module with an SC_THREAD
job runner. Callers push a std::packaged_task onto its queue from
any thread and optionally wait on the result. The SC_THREAD runs on
the owning simcontext's thread, so a b_transport call whose body
uses wait() to model timing can still do so correctly.
Wire it into tlm_utils::simple_target_socket:
* b_transport: if the caller is on a foreign simcontext, bounce
through the socket's on_context member.
* nb_transport_fw (forward): same.
Other TLM APIs (transport_dbg, get_direct_mem_ptr, the backward
nb_transport_bw, peq_with_get / peq_with_cb_and_phase) are not
routed. Those are considered the model writer's thread-safety
responsibility - consistent with the general rule that the kernel
provides mechanisms, the user's model chooses policy.
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
sc_ob_event ("on-boundary event") fires either when the simulator
arrives at the requested notification time, or when the only pending
activity is at/after that time. Useful as the underlying primitive
for sc_sync_window, where a side needs to step forward to the next
window boundary regardless of whether it has its own events at that
time.
Implemented as an sc_module + sc_event, with an SC_THREAD that
suspends and is woken via sc_register_stage_callback / SC_POST_UPDATE
when the simulator reaches the notification time.
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
sc_sync_windowed is a primitive-channel pair used to keep two
cooperating simcontexts in lock-step within a configurable time
window. Each side advertises a {from, to} time window to its peer
via async_set_window (cross-thread safe). Step-helper handles the
suspend/resume at the window boundary; sweep-helper drives the
"swept past from" time advance. Supports two policies:
sc_sync_policy_in_sync - quantum follows pending activity
sc_sync_policy_tlm_quantum - quantum from tlm_quantumkeeper
Both attach themselves as suspending channels of their owning
simcontext (async_attach_suspending), so a sim never starves out
while it has a sync_window peer that may yet send work.
Used by sc_concurrent (next patch) to bind a parent and a child
simcontext together.
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
…example
sc_concurrent<T, SYNC_POLICY> marks an SC_MODULE as safe to run
concurrently with the rest of the elaboration. It wraps T in a
sibling simcontext: an _internal_simcontext_handler constructs the
child simcontext, a control_module inside it owns a std::thread that
runs the child's sc_start, and a sc_sync_windowed pair (added in the
previous patch) keeps the two in time-lockstep. The module T
itself is constructed with sc_curr_simcontext temporarily switched
to the child.
SC_ALLOW_CONCURRENCY(mod, policy) is the user-facing macro. "Allow"
rather than "parallel" because whether the runtime actually exploits
the concurrency permission to run on a separate thread is
implementation-defined; the user's contract is only that the module
is safe to be run concurrently.
Author-side opt-out via sc_concurrent_safe<T>:
Class template sc_concurrent_safe<T> defaults to std::true_type
(every module is safe). A module author who knows their module
is fundamentally incompatible with concurrent execution specialises
the trait to std::false_type; a static_assert in sc_concurrent then
catches any wrapping attempt at compile time. The static_assert
is part of the class template itself, so it fires on a plain
pass-through implementation of sc_concurrent as well.
Report-handler config inheritance:
control_module captures the parent thread's report-handler
configuration in start_of_simulation() (via
sc_report_handler::snapshot_config) and applies it on the child
thread first thing in sc_context_start (via apply_config). This
means a custom handler / verbosity / log_file_name / suppress
policy installed in sc_main is automatically picked up by the
child simcontext's thread, instead of the child seeing defaults.
Counters (sev_call_count) are deliberately NOT snapshotted - the
child starts with fresh counts.
Bidirectional stop propagation:
* parent -> child: control_module's end_of_simulation() fires a
terminator_pc (a kernel sc_prim_channel in the child's registry)
whose async_request_update causes child's sc_stop(). Existing
mechanism.
* child -> parent: control_module also registers its stage_callback
for SC_POST_END_OF_SIMULATION in the child's stage-callback
registry (from sc_context_start). When the child's end-of-sim
fires, the stage_callback - discriminating on sc_curr_simcontext
- notifies m_end_ev (an event that lives in the parent's
simcontext). Cross-context notify routes via run_update_async
to the parent, where SC_METHOD(end_thread) fires and calls
parent-side sc_stop().
Keep-alive (neither side exits on starvation while the other has
work) is provided implicitly by sc_sync_windowed: each side
async_attach_suspending's its own simcontext's registry.
Plus an example in examples/sysc/concurrent/concurrent_test.cpp
demonstrating a parallel module and a normal module sharing a
sc_signal and a TLM b_transport.
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
When sibling simcontexts share an sc_object_manager (the parallel-sim
design introduced by sc_concurrent), both threads can end up
concurrently constructing sc_objects, firing stage callbacks and
tearing down ports. Three classes of problem surface; this patch
addresses all of them.
Shared name-table races
-----------------------
m_instance_table (the name -> sc_object/sc_event map) is genuinely
shared: names must be globally unique across all simcontexts, lookup
by name must see every registration. The existing mutex covered the
mutating methods (insert_event/object, remove_event/object,
insert/remove_external_name, create_name). The readers
(name_exists, get_name, find_event, find_object, first_object,
next_object) were unlocked, so ThreadSanitizer flagged them against
the mutating writers. Extend the lock to cover every path that
reads or writes m_instance_table.
create_name holds the mutex and calls name_exists (which now also
locks) and sc_get_curr_simcontext()->active_object() (which can
re-enter sc_object_manager). Make the mutex std::recursive_mutex so
these re-entries are safe.
Per-thread construction context
-------------------------------
m_object_stack and m_module_name_stack are not shared state; they are
the *current thread's* construction-time hierarchy navigation. Their
uses (sc_hierarchy_scope push/pop, sc_module_init push / ~sc_module
pop, sc_module_name push/pop, sc_object_init reading active_object()
to set m_parent) are always matched pairs within a single call on a
single thread.
When two sibling simcontext threads both do construction work
concurrently - which happens normally during parallel elaborate() and
prepare_to_simulate() on child threads - they would otherwise see and
mutate a single shared stack, giving every new sc_object the wrong
m_parent and racing on the owning sc_object_host's m_child_objects
vector.
Move both stacks to static thread_local storage. Each thread has its
own construction scope, single-threaded elaboration behaves exactly
as before, and no cross-thread stack lock is needed. The downstream
effect is that sentinels and the kernel's sc_invoke_method created on
a child thread correctly parent to that child's own simcontext rather
than latching onto whatever the parent thread last pushed.
Unique sentinel names per simcontext
------------------------------------
sc_runnable::init lazily allocates a pair of kernel-internal
list-head sentinels ("methods_push_head", "threads_push_head") via
new sc_method_process / new sc_thread_process. One pair per
simcontext. With a shared instance_table, two simcontexts would
race to register the same name; the loser was renamed to
"methods_push_head_0" and a "Warning: object already exists" report
fired non-deterministically based on thread scheduling.
Give each simcontext its own sentinel namespace by anchoring the
sentinels under the simcontext's first top-level sc_module at
construction time. A new sc_simcontext::name() returns the
basename of that module (or SC_DEFAULT_SIMCONTEXT_NAME_, a
kernel-internal fallback token, before any module is registered),
and a sister sc_simcontext::first_top_level_host() returns the
sc_object_host* used as the hierarchy anchor. Skipping non-module
children (sc_signal, sc_async_runnable_helper, etc.) keeps the
name tied to user intent rather than whichever helper happened to
register first.
Before sentinel construction, push the first top-level module onto
the thread-local hierarchy stack; sc_object_init then picks it up
as m_parent, giving the sentinels full names like
"prod.methods_push_head". detach() immediately afterwards unhooks
them from the parent's child list and the shared instance_table;
only the already-unique full name lives on.
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
Three small integration tests exercising the parallel-sim machinery
introduced in earlier patches. Each runs under the standard regression
harness (golden-log comparison, exit-code-based pass/fail).
cross_context_write
Producer in an SC_ALLOW_CONCURRENCY child simcontext writes a
parent-owned sc_signal that the parent's consumer reads. Verifies
sc_signal::write from a foreign simcontext routes correctly via
run_update_async, the receiver observes every write, and ordering
is preserved.
child_stop
Child SC_ALLOW_CONCURRENCY module calls sc_stop() from its own
thread; parent has a self-rearming ticker that would otherwise run
forever. Verifies child->parent stop propagation cleanly halts the
whole simulation within a bounded number of parent ticks.
multiple_modules
Two independent SC_ALLOW_CONCURRENCY producers, each in its own
child simcontext, feeding a single parent consumer over its own
sc_signal. Verifies multiple sibling simcontexts coexist
correctly and each cross-context write stream lands intact.
Each test directory has a golden/ subdir picked up automatically by
the regression harness's discover_regression_tests().
Signed-off-by: Mark Burton <mburton@qti.qualcomm.com>
markfoodyburton
force-pushed
the
mburton/psystemc
branch
from
May 14, 2026 08:10
7fb25b8 to
b82a6aa
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.