Skip to content
Open
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ set (SystemCCCI_SOVERSION "${SystemCCCI_VERSION_MAJOR}.${SystemCCCI_VERSION_MINO
# lib-<target-arch>. (default: OFF)

option(CCI_ENABLE_CFG "Build SystemCCI configuration library" ON)
option(CCI_ENABLE_THREAD_SAFETY "Enable thread-safe broker and parameter access" OFF)
option(SYSTEMCCCI_BUILD_TESTS "Build tests & examples" ON)
option(CCI_ENABLE_INSPECTION "Build SystemCCI inspection library" ON)
option(BUILD_SOURCE_DOCUMENTATION "Build source documentation with Doxygen." OFF)
Expand Down
9 changes: 6 additions & 3 deletions configuration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,14 @@ if (NOT SystemC_TARGET_ARCH)
endif (NOT SystemC_TARGET_ARCH)


list (APPEND CMAKE_PREFIX_PATH /opt/systemc)
list (APPEND CMAKE_PREFIX_PATH /opt/systemc $ENV{SYSTEMC_HOME})

IF (NOT SystemCLanguage_FOUND AND NOT TARGET SystemC::systemc)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
find_package(SystemCLanguage REQUIRED)
find_package(SystemCLanguage CONFIG QUIET NO_CMAKE_PACKAGE_REGISTRY)
if (NOT TARGET SystemC::systemc)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
find_package(SystemCLanguage REQUIRED)
endif()
ENDIF ()
message (STATUS "Using SystemC ${SystemCLanguage_VERSION} (${SystemCLanguage_DIR})")
IF ( "${SystemC_CXX_STANDARD}" AND NOT "${SystemC_CXX_STANDARD}" EQUAL "${CMAKE_CXX_STANDARD}")
Expand Down
5 changes: 4 additions & 1 deletion configuration/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,12 @@ endfunction()
if (SYSTEMCCCI_BUILD_TESTS)
include_directories(cci/common/inc/)


file(GLOB EXAMPLES cci/ex*)
foreach(example ${EXAMPLES})
# ex20_Thread_Safety requires CCI_ENABLE_THREAD_SAFETY
if ("${example}" MATCHES "ex20_Thread_Safety" AND NOT CCI_ENABLE_THREAD_SAFETY)
continue()
endif()
add_test_exe(${example})
endforeach()
endif()
235 changes: 235 additions & 0 deletions configuration/examples/cci/ex20_Thread_Safety/ex20_Thread_Safety.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*****************************************************************************

Licensed to Accellera Systems Initiative Inc. (Accellera) under one or
more contributor license agreements. See the NOTICE file distributed
with this work for additional information regarding copyright ownership.
Accellera licenses this file to you under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with the
License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing
permissions and limitations under the License.

****************************************************************************/

/**
* @file ex20_Thread_Safety.cpp
* @brief Demonstrates CCI thread-safety issues when accessing the broker
* from multiple threads concurrently.
*
* This test creates a broker with preset values, then spawns worker threads
* that read presets and param handles concurrently. Without thread-safety
* mechanisms in the broker, this produces data races (detectable with TSan).
*
* It also demonstrates using a custom originator to access the broker
* from non-SystemC threads, avoiding the sc_get_current_object() issue.
*/

#include <cci_configuration>
#include <systemc>

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

/* Simple module that creates a CCI param */
SC_MODULE(my_module) {
cci::cci_param<int> p_value;

SC_CTOR(my_module)
: p_value("value", 42, "a test parameter")
{
SC_REPORT_INFO("my_module", "Constructed");
}
};

/* Worker thread function: reads broker state concurrently.
* Uses a pre-created broker handle with a custom originator
* (not dependent on sc_get_current_object). */
static void worker_read(cci::cci_broker_handle broker, int id,
std::atomic<int>& errors) {
try {
for (int i = 0; i < 100; i++) {
/* Read preset values — concurrent with other readers and writers */
auto val = broker.get_preset_cci_value("mod.value");

/* Get param handles — concurrent access to param registry */
auto handles = broker.get_param_handles();

/* Get unconsumed presets — iterates internal maps */
auto presets = broker.get_unconsumed_preset_values();
}
} catch (const std::exception& e) {
std::cerr << "Thread " << id << " exception: " << e.what() << "\n";
errors++;
}
}

/* Worker thread function: writes preset values concurrently */
static void worker_write(cci::cci_broker_handle broker, int id,
std::atomic<int>& errors) {
try {
for (int i = 0; i < 100; i++) {
/* Write preset values — concurrent with readers and other writers */
broker.set_preset_cci_value(
"dynamic_param_" + std::to_string(id) + "_" + std::to_string(i),
cci::cci_value(i));

/* Also read while writing */
auto val = broker.get_preset_cci_value("mod.value");
}
} catch (const std::exception& e) {
std::cerr << "Thread " << id << " exception: " << e.what() << "\n";
errors++;
}
}

int sc_main(int argc, char *argv[]) {
/* Create broker and register it */
cci_utils::consuming_broker global_broker("Global Broker");
cci::cci_register_broker(global_broker);

/* Create a custom originator for non-SystemC threads */
cci::cci_originator worker_orig("worker_thread");
auto worker_broker = global_broker.create_broker_handle(worker_orig);

/* Set some initial preset values */
cci::cci_originator main_orig("sc_main");
auto main_broker = global_broker.create_broker_handle(main_orig);
main_broker.set_preset_cci_value("mod.value", cci::cci_value(100));
main_broker.set_preset_cci_value("mod.other", cci::cci_value(200));

/* Create a module (consumes the preset) */
my_module mod("mod");

SC_REPORT_INFO("sc_main", "Starting concurrent access test...");

std::atomic<int> errors{0};
const int NUM_READERS = 4;
const int NUM_WRITERS = 2;

/* Spawn reader and writer threads — all using the worker_broker handle
* with the custom originator (no sc_get_current_object dependency) */
std::vector<std::thread> threads;

for (int i = 0; i < NUM_READERS; i++) {
threads.emplace_back(worker_read, worker_broker, i, std::ref(errors));
}
for (int i = 0; i < NUM_WRITERS; i++) {
threads.emplace_back(worker_write, worker_broker,
NUM_READERS + i, std::ref(errors));
}

/* Join all threads */
for (auto& t : threads) {
t.join();
}

if (errors == 0) {
SC_REPORT_INFO("sc_main",
"All threads completed without exceptions. "
"NOTE: absence of crashes does NOT prove thread safety. "
"Run with ThreadSanitizer (TSan) to detect data races.");
} else {
SC_REPORT_ERROR("sc_main",
("Threads reported " + std::to_string(errors.load()) +
" error(s)").c_str());
}

/* Also demonstrate: reading params from a non-SystemC thread works
* with the custom originator — no sc_get_current_object needed */
std::thread param_reader([&worker_broker]() {
auto h = worker_broker.get_param_handle("mod.value");
if (h.is_valid()) {
int val = h.get_cci_value().get_int();
std::cout << "Worker thread read mod.value = " << val << "\n";
}
});
param_reader.join();

/* Performance measurement: read-heavy workload */
SC_REPORT_INFO("sc_main", "Starting performance measurement...");

const int ITERATIONS = 100000;
const int NUM_THREADS = 4;

/* Single-threaded baseline */
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERATIONS; i++) {
auto h = main_broker.get_param_handle("mod.value");
if (h.is_valid()) h.get_cci_value();
}
auto t1 = std::chrono::high_resolution_clock::now();
auto single_us = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count();

/* Multi-threaded: same total work split across threads */
auto t2 = std::chrono::high_resolution_clock::now();
{
std::vector<std::thread> perf_threads;
for (int t = 0; t < NUM_THREADS; t++) {
perf_threads.emplace_back([&worker_broker, ITERATIONS, NUM_THREADS]() {
for (int i = 0; i < ITERATIONS / NUM_THREADS; i++) {
auto h = worker_broker.get_param_handle("mod.value");
if (h.is_valid()) h.get_cci_value();
}
});
}
for (auto& t : perf_threads) t.join();
}
auto t3 = std::chrono::high_resolution_clock::now();
auto multi_us = std::chrono::duration_cast<std::chrono::microseconds>(t3 - t2).count();

std::cout << "Performance: " << ITERATIONS << " get_param_handle + get_cci_value\n";
std::cout << " Single-threaded: " << single_us << " us ("
<< (single_us * 1000 / ITERATIONS) << " ns/op)\n";
std::cout << " Multi-threaded (" << NUM_THREADS << " threads): "
<< multi_us << " us ("
<< (multi_us * 1000 / ITERATIONS) << " ns/op)\n";
std::cout << " Overhead: " << (multi_us * 100 / std::max(single_us, (decltype(single_us))1) - 100)
<< "%\n";

/* Cached handle benchmark — more realistic (handle created once, reused) */
auto t4 = std::chrono::high_resolution_clock::now();
{
auto h = main_broker.get_param_handle("mod.value");
for (int i = 0; i < ITERATIONS; i++) {
if (h.is_valid()) h.get_cci_value();
}
}
auto t5 = std::chrono::high_resolution_clock::now();
auto cached_single_us = std::chrono::duration_cast<std::chrono::microseconds>(t5 - t4).count();

auto t6 = std::chrono::high_resolution_clock::now();
{
std::vector<std::thread> perf_threads;
for (int t = 0; t < NUM_THREADS; t++) {
perf_threads.emplace_back([&worker_broker, ITERATIONS, NUM_THREADS]() {
auto h = worker_broker.get_param_handle("mod.value");
for (int i = 0; i < ITERATIONS / NUM_THREADS; i++) {
if (h.is_valid()) h.get_cci_value();
}
});
}
for (auto& t : perf_threads) t.join();
}
auto t7 = std::chrono::high_resolution_clock::now();
auto cached_multi_us = std::chrono::duration_cast<std::chrono::microseconds>(t7 - t6).count();

std::cout << "\nCached handle: " << ITERATIONS << " get_cci_value (handle reused)\n";
std::cout << " Single-threaded: " << cached_single_us << " us ("
<< (cached_single_us * 1000 / ITERATIONS) << " ns/op)\n";
std::cout << " Multi-threaded (" << NUM_THREADS << " threads): "
<< cached_multi_us << " us ("
<< (cached_multi_us * 1000 / ITERATIONS) << " ns/op)\n";
std::cout << " Overhead: " << (cached_multi_us * 100 / std::max(cached_single_us, (decltype(cached_single_us))1) - 100)
<< "%\n";

SC_REPORT_INFO("sc_main", "Test complete.");
return (errors == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
1 change: 1 addition & 0 deletions configuration/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ target_compile_definitions (
$<$<BOOL:${WIN32}>:WIN32>
$<$<AND:$<BOOL:${BUILD_SHARED_LIBS}>,$<OR:$<BOOL:${WIN32}>,$<BOOL:${CYGWIN}>>>:
SC_WIN_DLL>
$<$<BOOL:${CCI_ENABLE_THREAD_SAFETY}>:CCI_THREAD_SAFE>
PRIVATE
SC_BUILD
SC_INCLUDE_FX
Expand Down
18 changes: 18 additions & 0 deletions configuration/src/cci/cfg/cci_originator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,29 @@
#include "cci/cfg/cci_originator.h"

#include <cstring>
#include <thread>

#include "cci/cfg/cci_config_macros.h"
#include "cci/cfg/cci_report_handler.h"

CCI_OPEN_NAMESPACE_

/// Captured SystemC thread ID — set on first originator construction.
/// Used to detect non-SystemC threads where sc_get_current_object()
/// is unreliable.
static std::thread::id sysc_thread_id{};
static bool sysc_thread_id_set = false;

static bool is_on_sysc_thread() {
if (!sysc_thread_id_set) {
// First call — assume we're on the SystemC thread
sysc_thread_id = std::this_thread::get_id();
sysc_thread_id_set = true;
return true;
}
return std::this_thread::get_id() == sysc_thread_id;
}

cci_originator::cci_originator(const std::string& originator_name)
: m_originator_obj()
, m_originator_str()
Expand Down Expand Up @@ -143,6 +160,7 @@ bool cci_originator::operator<(const cci_originator& originator) const {
}

sc_core::sc_object *cci_originator::current_originator_object() {
if (!is_on_sysc_thread()) return NULL;
return sc_core::sc_get_current_object();
}

Expand Down
9 changes: 6 additions & 3 deletions configuration/src/cci/cfg/cci_originator.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#define CCI_CFG_CCI_ORIGINATOR_H_INCLUDED_

#include "cci/core/cci_cmnhdr.h"
#include "cci/cfg/cci_config_macros.h"

CCI_OPEN_NAMESPACE_

Expand Down Expand Up @@ -48,11 +49,13 @@ class cci_originator
: m_originator_obj(), m_originator_str() {}

public:
/// Default Constructor assumes current module is the originator
/// Default Constructor assumes current module is the originator.
/// Outside the SystemC hierarchy (e.g. non-SystemC threads),
/// creates an originator with the unknown/default name.
inline cci_originator()
: m_originator_obj(current_originator_object()),
m_originator_str(NULL) {
check_is_valid();
m_originator_str(m_originator_obj ? NULL
: new std::string(CCI_UNKNOWN_ORIGINATOR_STRING_)) {
}

/// Constructor with an originator name
Expand Down
11 changes: 10 additions & 1 deletion configuration/src/cci/cfg/cci_param_typed.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,21 @@ class cci_param_typed_handle;
///@cond CCI_HIDDEN_FROM_DOXYGEN
namespace cci_impl {
/// implementation defined helper to set/reset a boolean flag
#ifdef CCI_THREAD_SAFE
struct scoped_true {
explicit scoped_true(std::atomic<bool>& ref) : ref_(ref) { ref_ = true; }
~scoped_true() { ref_ = false; }
private:
std::atomic<bool>& ref_;
};
#else
struct scoped_true {
explicit scoped_true(bool& ref) : ref_(ref) { ref_ = true; }
~scoped_true() { ref_ = false; }
private:
bool& ref_;
}; // class scoped_true
};
#endif
} // namespace cci_impl
///@endcond

Expand Down
Loading
Loading