From b407b127fdb90444affe9398faff6fab80da68ce Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 3 Apr 2025 16:16:05 +0200 Subject: [PATCH 01/50] Add sc_log Signed-off-by: Mark Burton --- src/CMakeLists.txt | 4 +- src/sc_log/sc_log.cpp | 93 ++++++++ src/sc_log/sc_log.h | 137 +++++++++++ src/sc_log/sc_log_types.h | 316 +++++++++++++++++++++++++ src/sysc/kernel/sc_module.h | 1 + src/sysc/kernel/sc_simcontext.h | 22 ++ src/systemc | 2 + tests/systemc/sc_log/golden/test01.log | 0 tests/systemc/sc_log/test01.cpp | 101 ++++++++ 9 files changed, 675 insertions(+), 1 deletion(-) create mode 100644 src/sc_log/sc_log.cpp create mode 100644 src/sc_log/sc_log.h create mode 100644 src/sc_log/sc_log_types.h create mode 100644 tests/systemc/sc_log/golden/test01.log create mode 100644 tests/systemc/sc_log/test01.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a6cca4957..51abb3eac 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,7 +53,7 @@ function(add_systemc_library libName scBuildDefine) target_compile_features( ${libName} PUBLIC - cxx_std_17) + cxx_std_20) target_compile_definitions ( ${libName} @@ -418,6 +418,8 @@ set(SYSTEMC_CORE_SRC sysc/packages/qt/qt.h sysc/packages/qt/qtmd.h > + # sc_log + sc_log/sc_log.cpp ) set(SYSTEMC_CORE_SRC_PRIVATE diff --git a/src/sc_log/sc_log.cpp b/src/sc_log/sc_log.cpp new file mode 100644 index 000000000..a115863ed --- /dev/null +++ b/src/sc_log/sc_log.cpp @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright 2017-2022 MINRES Technologies GmbH + * + * Licensed 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. + *******************************************************************************/ +/* + * sc_report.cpp + * + * Created on: 19.09.2017 + * Author: eyck@minres.com + * + * THIS FILE IS INTENDED TO BE UP-STREAMED + */ + +#include "sc_log/sc_log_types.h" +#include "sysc/kernel/sc_simcontext.h" +#include + +namespace { +// Making this thread_local could cause thread copies of the same cache +// entries, but more likely naming will be thread local too, and this avoids +// races in the unordered_map + +#ifdef DISABLE_REPORT_THREAD_LOCAL +std::unordered_map lut; +#else +thread_local std::unordered_map lut; +#endif + +// BKDR hash algorithm +auto char_hash(char const *str) -> uint64_t { + constexpr unsigned int seed = 131; // 31 131 1313 13131131313 etc// + uint64_t hash = 0; + while (*str) { + hash = (hash * seed) + (*str); + str++; + } + return hash; +} +} // namespace + +sc_log::log_levels +sc_log::sc_log_logger_cache::get_log_verbosity_cached(const char *scname, + const char *tname = "") { + if (level != sc_log::log_levels::UNSET) { + return level; + } + + if (!scname && features.size()) + scname = features[0].c_str(); + if (!scname) + scname = ""; + + type = std::string(scname); + + return sc_core::sc_get_curr_simcontext()->get_log_verbosity(*this, scname, + tname); +} + +auto sc_log::get_log_verbosity(char const *str) -> sc_log::log_levels { + auto k = char_hash(str); + auto it = lut.find(k); + if (it != lut.end()) { + return it->second; + } + + sc_log::sc_log_logger_cache tmp; + lut[k] = tmp.get_log_verbosity_cached(str); + return lut[k]; +} + +sc_log::sc_log_global_logger_handler::sc_log_global_logger_handler() { + std::function + fn = [&](sc_log::sc_log_logger_cache &logger, const char *sc_name, + const char *t_name) -> sc_log::log_levels { + return operator()(logger, sc_name, t_name); + }; + ::sc_core::sc_get_curr_simcontext()->set_log_verbosity_fn(fn); + ::sc_core::sc_report_handler::set_verbosity_level( + sc_core::SC_DEBUG); // Set the level in the core to DEBUG such that the + // handler can manage all levels of verbosity +} diff --git a/src/sc_log/sc_log.h b/src/sc_log/sc_log.h new file mode 100644 index 000000000..55b9ea360 --- /dev/null +++ b/src/sc_log/sc_log.h @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright 2016-2022 MINRES Technologies GmbH + * + * Licensed 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. + *******************************************************************************/ +/* + * + * THIS FILE IS INTENDED TO BE UP-STREAMED + */ +#ifndef _SC_LOG_REPORT_H_ +#define _SC_LOG_REPORT_H_ + +#define SC_HAS_SC_LOG + +#include +#include +#include +#include +#include +#include + +#include + +// must be global for macro to work. +static const char *_SC_LOG_FMT_EMPTY_STR = ""; + +/** + * logging macros + */ + +/** + * Boilerplate convenience macros + */ +#define _SC_LOG_CAT(a, ...) _SC_LOG_PRIMITIVE_CAT(a, __VA_ARGS__) +#define _SC_LOG_PRIMITIVE_CAT(a, ...) a##__VA_ARGS__ + +#define _SC_LOG_IIF(c) _SC_LOG_PRIMITIVE_CAT(_SC_LOG_IIF_, c) +#define _SC_LOG_IIF_0(t, ...) __VA_ARGS__ +#define _SC_LOG_IIF_1(t, ...) t + +#define _SC_LOG_CHECK_N(x, n, ...) n +#define _SC_LOG_CHECK(...) _SC_LOG_CHECK_N(__VA_ARGS__, 0, ) +#define _SC_LOG_PROBE(x) x, 1, + +#define _SC_LOG_EXPAND(...) __VA_ARGS__ + +#define _SC_LOG_FIRST_ARG(f, ...) f +#define _SC_LOG_POP_ARG(f, ...) __VA_ARGS__ + +#define _SC_LOG_IS_PAREN(x) _SC_LOG_CHECK(_SC_LOG_IS_PAREN_PROBE x) +#define _SC_LOG_IS_PAREN_PROBE(...) _SC_LOG_PROBE(~) +/********/ + +/* default logger cache name */ +#define SC_LOG_HANDLE_NAME(x) _SC_LOG_CAT(SC_LOG_LOG_LEVEL_CACHE, x) + +/* User interface macros */ +#define SCMOD this->sc_core::sc_module::name() +#define SC_LOG_HANDLE(...) \ + sc_log::sc_log_logger_cache _SC_LOG_IIF( \ + _SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__)))( \ + SC_LOG_HANDLE_NAME( \ + _SC_LOG_EXPAND(_SC_LOG_FIRST_ARG _SC_LOG_FIRST_ARG(__VA_ARGS__))), \ + SC_LOG_HANDLE_NAME()) = { \ + sc_log::log_levels::UNSET, \ + "", \ + {_SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__)))( \ + _SC_LOG_POP_ARG(__VA_ARGS__), ##__VA_ARGS__)}} + +#define SC_LOG_HANDLE_VECTOR(NAME) \ + std::vector SC_LOG_HANDLE_NAME(NAME) +#define SC_LOG_HANDLE_VECTOR_PUSH_BACK(NAME, ...) \ + SC_LOG_HANDLE_NAME(NAME).push_back( \ + {sc_log::log_levels::UNSET, "", {__VA_ARGS__}}); + +// critical thing is that the initial if 'fails' as soon as possible - if it is +// going to pass, we have all the time we want, as we will be logging anyway +// This HAS to be done as a macro, because the first argument may be a string +// or a cache'd level + +/*** Helper macros for SCP_ report macros ****/ +#define SCP_VBSTY_CHECK_CACHED(lvl, features, cached, ...) \ + (cached.level >= lvl) && \ + (cached.get_log_verbosity_cached(sc_log::call_sc_name_fn()(this), \ + typeid(*this).name()) >= lvl) + +#define SCP_VBSTY_CHECK_UNCACHED(lvl, ...) \ + (::sc_log::get_log_verbosity(__VA_ARGS__) >= lvl) + +#define SCP_VBSTY_CHECK(lvl, ...) \ + _SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__))) \ + (SCP_VBSTY_CHECK_CACHED( \ + lvl, _SC_LOG_FIRST_ARG(__VA_ARGS__), \ + SC_LOG_HANDLE_NAME( \ + _SC_LOG_EXPAND(_SC_LOG_FIRST_ARG _SC_LOG_FIRST_ARG(__VA_ARGS__)))), \ + SCP_VBSTY_CHECK_UNCACHED(lvl, ##__VA_ARGS__)) + +#define SCP_GET_FEATURES(...) \ + _SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__))) \ + (_SC_LOG_FIRST_ARG _SC_LOG_EXPAND((_SC_LOG_POP_ARG( \ + __VA_ARGS__, \ + SC_LOG_HANDLE_NAME( \ + _SC_LOG_EXPAND(_SC_LOG_FIRST_ARG _SC_LOG_FIRST_ARG(__VA_ARGS__))) \ + .type))), \ + __VA_ARGS__) + +#define _SC_LOG_FMT_EMPTY_STR(...) std::format(__VA_ARGS__) + +#define SCP_MSG(lvl, ...) \ + ::sc_log::ScLogger<::sc_core::SC_INFO, false>(__FILE__, __LINE__, lvl) \ + .type(SCP_GET_FEATURES(__VA_ARGS__)) \ + .get() \ + << _SC_LOG_FMT_EMPTY_STR +/*** End HELPER Macros *******/ + +#define SC_LOG_AT(lvl, ...) \ + if (SCP_VBSTY_CHECK(lvl, __VA_ARGS__)) \ + SCP_MSG(lvl, __VA_ARGS__) + +#define SC_CRITICAL(...) SC_LOG_AT(sc_log::log_levels::CRITICAL, __VA_ARGS__) +#define SC_WARN(...) SC_LOG_AT(sc_log::log_levels::WARN, __VA_ARGS__) +#define SC_INFO(...) SC_LOG_AT(sc_log::log_levels::INFO, __VA_ARGS__) +#define SC_DEBUG(...) SC_LOG_AT(sc_log::log_levels::DEBUG, __VA_ARGS__) +#define SC_TRACE(...) SC_LOG_AT(sc_log::log_levels::TRACE, __VA_ARGS__) + +/** @} */ // end of sc_log-report +#endif /* _SC_LOG_REPORT_H_ */ diff --git a/src/sc_log/sc_log_types.h b/src/sc_log/sc_log_types.h new file mode 100644 index 000000000..349d984f8 --- /dev/null +++ b/src/sc_log/sc_log_types.h @@ -0,0 +1,316 @@ +/******************************************************************************* + * Copyright 2016-2022 MINRES Technologies GmbH + * + * Licensed 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. + *******************************************************************************/ +/* + * + * THIS FILE IS INTENDED TO BE UP-STREAMED + */ +#ifndef _SC_LOG_REPORT_TYPES_H_ +#define _SC_LOG_REPORT_TYPES_H_ + +#include +#include +#include +#include +#include +#include + +#include + +/** \ingroup sc_log + * @{ + */ +/**@{*/ +//! @brief Logging utilities +namespace sc_log { + +/************************ + * Provide a set of names and conversions that are suitable for logging levels + *based on SystemC "verbosity's" + ************************/ + +enum class log_levels { + NONE = sc_core::SC_NONE, + CRITICAL = sc_core::SC_NONE, + WARN = sc_core::SC_LOW, + INFO = sc_core::SC_MEDIUM, + DEBUG = sc_core::SC_HIGH, + TRACE = sc_core::SC_DEBUG, + + UNSET = INT_MAX +}; + +const static std::map log_level_map = { + {log_levels::CRITICAL, "CRITICAL"}, {log_levels::NONE, "NONE"}, + {log_levels::WARN, "WARN"}, {log_levels::INFO, "INFO"}, + {log_levels::DEBUG, "DEBUG"}, {log_levels::TRACE, "TRACE"}}; + +/** + * @fn log as_log(int) + * @brief safely convert an integer into a log level + * + * @param logLevel the logging level + * @return the log level + */ +inline log_levels as_log(int logLevel) { + auto m = log_level_map; + for (auto l : m) { + if (logLevel <= static_cast(l.first)) { + return l.first; + } + } + return log_levels::TRACE; +} + +/** + * @fn log as_log(std::string) + * @brief safely convert a string into a log level + * + * @param logName the string name for the log level + * @return the log level + */ +inline log_levels as_log(std::string logName) { + auto m = log_level_map; + for (auto l : m) { + if (logName == l.second) + return l.first; + } + return log_levels::TRACE; +} +/** + * @fn std::istream& operator >>(std::istream&, log&) + * @brief read a log level from input stream e.g. used by boost::lexical_cast + * + * @param is input stream holding the string representation + * @param val the value holding the resulting value + * @return the input stream + */ +inline std::istream &operator>>(std::istream &is, log_levels &val) { + std::string buf; + is >> buf; + val = as_log(buf); + return is; +} +/** + * @fn std::ostream& operator <<(std::ostream&, const log&) + * @brief output the textual representation of the log level + * + * @param os output stream + * @param val logging level + * @return reference to the stream for chaining + */ +inline std::ostream &operator<<(std::ostream &os, log_levels const &val) { + auto m = log_level_map; + os << m[val]; + return os; +} + +/* Convenience function to allow useage outside of SystemC heirarchy */ +class call_sc_name_fn { + template + static auto test(T *p) + -> decltype(p->sc_core::sc_module::name(), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + template + static constexpr bool has_method = decltype(test(nullptr))::value; + +public: + // define a function IF the method exists + template + auto operator()(TYPE *p) const + -> std::enable_if_t, const char *> { + return p->sc_core::sc_module::name(); + } + + // define a function IF NOT the method exists + template + auto operator()(TYPE *p) const + -> std::enable_if_t, const char *> { + return nullptr; + } +}; + +/******************/ + +/** + * @brief cached logging information used in the (logger) form. + * + */ +struct sc_log_logger_cache { + log_levels level = log_levels::UNSET; + std::string type = ""; + std::vector features; + + /** + * @brief Initialize the verbosity cache and/or return the cached value. + * + * @return log + */ + log_levels get_log_verbosity_cached(const char *, const char *); +}; + +class sc_log_global_logger_handler { +public: + virtual log_levels operator()(struct sc_log_logger_cache &logger, + std::string_view scname, + const char *tname) const = 0; + sc_log_global_logger_handler(); +}; + +inline log_levels get_log_verbosity() { + return static_cast( + ::sc_core::sc_report_handler::get_verbosity_level()); +} +/** + * @fn sc_core::sc_verbosity get_log_verbosity(const char*) + * @brief get the scope-based verbosity level + * + * The function returns a scope specific verbosity level if defined (e.g. by + * using a CCI param named "log_level"). Otherwise the global verbosity level + * is being returned + * + * @param t the SystemC hierarchy scope name + * @return the verbosity level + */ + +log_levels get_log_verbosity(char const *t); +/** + * @fn sc_core::sc_verbosity get_log_verbosity(const char*) + * @brief get the scope-based verbosity level + * + * The function returns a scope specific verbosity level if defined (e.g. by + * using a CCI param named "log_level"). Otherwise the global verbosity level + * is being returned + * + * @param t the SystemC hierarchy scope name + * @return the verbosity level + */ +inline log_levels get_log_verbosity(std::string const &t) { + return get_log_verbosity(t.c_str()); +} + +/** + * @brief Return list of logging parameters that have been used + * + */ +std::vector get_logging_parameters(); + +/** + * @struct ScLogger + * @brief the logger class + * + * The ScLogger creates a RTTI based output stream to be used similar to + * std::cout + * + * @tparam SEVERITY + */ +template +struct ScLogger { + /** + * @fn ScLogger(const char*, int, int=sc_core::SC_MEDIUM) + * @brief + * + * @param file where the log entry originates + * @param line number where the log entry originates + * @param verbosity the log level + */ + ScLogger(const char *file, int line, + log_levels verbosity = sc_log::log_levels::INFO) + : t(nullptr), file(file), line(line), level(verbosity) {} + + ScLogger() = delete; + + ScLogger(const ScLogger &) = delete; + + ScLogger(ScLogger &&) = delete; + + ScLogger &operator=(const ScLogger &) = delete; + + ScLogger &operator=(ScLogger &&) = delete; + /** + * @fn ~ScLogger() + * @brief the destructor generating the SystemC report + * + * NB a destructor should not throw an exception, here we attempt to prevent + * the sc_report_handler from throwing The ScLogging interface is _ONLY_ for + * logging, simulation control should happen in user code. + */ + virtual ~ScLogger() noexcept(true) { + auto old = sc_core::sc_report_handler::set_actions(SEVERITY); + if (WITH_ACTIONS == false) { + sc_core::sc_report_handler::set_actions( + SEVERITY, old & ~(sc_core::SC_THROW | sc_core::SC_INTERRUPT | + sc_core::SC_STOP | sc_core::SC_ABORT)); + } + ::sc_core::sc_report_handler::report( + SEVERITY, t ? t : "SystemC", os.str().c_str(), + static_cast(level), file, line); + sc_core::sc_report_handler::set_actions(SEVERITY, old); + } + /** + * @fn ScLogger& type() + * @brief reset the category of the log entry + * + * @return reference to self for chaining + */ + inline ScLogger &type() { + this->t = nullptr; + return *this; + } + /** + * @fn ScLogger& type(const char*) + * @brief set the category of the log entry + * + * @param t type of th elog entry + * @return reference to self for chaining + */ + inline ScLogger &type(char const *t) { + this->t = const_cast(t); + return *this; + } + /** + * @fn ScLogger& type(std::string const&) + * @brief set the category of the log entry + * + * @param t type of th elog entry + * @return reference to self for chaining + */ + inline ScLogger &type(std::string const &t) { + this->t = const_cast(t.c_str()); + return *this; + } + /** + * @fn std::ostream& get() + * @brief get the underlying ostringstream + * + * @return the output stream collecting the log message + */ + inline std::ostream &get() { return os; }; + +protected: + std::ostringstream os{}; + char *t{nullptr}; + const char *file; + const int line; + const log_levels level; +}; + +} // namespace sc_log + +#define SC_LOG_LOG_LEVEL_CACHE _m_sc_log_log_level_cache_ + +/** @} */ // end of sc_log +#endif /* _SC_LOG_REPORT_H_ */ diff --git a/src/sysc/kernel/sc_module.h b/src/sysc/kernel/sc_module.h index eb8c4aae8..aa50814b2 100644 --- a/src/sysc/kernel/sc_module.h +++ b/src/sysc/kernel/sc_module.h @@ -89,6 +89,7 @@ class SC_API sc_module friend class sc_simcontext; friend class sc_initializer_function; public: + sc_log::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE; sc_simcontext* sc_get_curr_simcontext() { return simcontext(); } diff --git a/src/sysc/kernel/sc_simcontext.h b/src/sysc/kernel/sc_simcontext.h index 7788281f3..ec857278c 100644 --- a/src/sysc/kernel/sc_simcontext.h +++ b/src/sysc/kernel/sc_simcontext.h @@ -37,6 +37,7 @@ #include "sysc/kernel/sc_stage_callback_if.h" #include "sysc/utils/sc_hash.h" #include "sysc/utils/sc_pq.h" +#include "sc_log/sc_log_types.h" #include "sysc/communication/sc_host_mutex.h" @@ -77,6 +78,7 @@ class sc_cthread_process; class sc_thread_process; class sc_reset_finder; class sc_stub_registry; +class sc_log_global_logger_handler; extern sc_simcontext* sc_get_curr_simcontext(); @@ -181,6 +183,8 @@ class SC_API sc_simcontext friend class sc_prim_channel; friend class sc_cthread_process; friend class sc_thread_process; + friend class sc_log::sc_log_global_logger_handler; + friend struct sc_log::sc_log_logger_cache; friend SC_API sc_dt::uint64 sc_delta_count(); friend SC_API const std::vector& sc_get_top_level_events( const sc_simcontext* simc_p); @@ -329,6 +333,22 @@ class SC_API sc_simcontext void pre_suspend() const; void post_suspend() const; + protected: + void set_log_verbosity_fn( + std::function + fn) { + dynamic_log_verbosity = fn; + } + sc_log::log_levels get_log_verbosity(sc_log::sc_log_logger_cache &logger, + const char *sc_name, + const char *typ_name) { + if (dynamic_log_verbosity) + return dynamic_log_verbosity(logger, sc_name, typ_name); + else + return sc_log::as_log(sc_report_handler::get_verbosity_level()); + } + private: void hierarchy_push(sc_object_host*); sc_object_host* hierarchy_pop(); @@ -378,6 +398,8 @@ class SC_API sc_simcontext inline void set_simulation_status(sc_status status); + std::function dynamic_log_verbosity; + private: enum execution_phases { diff --git a/src/systemc b/src/systemc index 23d131175..a599aff51 100644 --- a/src/systemc +++ b/src/systemc @@ -134,6 +134,8 @@ #include "sysc/utils/sc_vector.h" #include "sysc/utils/sc_string.h" +#include "sc_log/sc_log.h" + #endif // !defined(SYSTEMC_INCLUDED) #ifdef SC_INCLUDE_EXTRA_STD_HEADERS diff --git a/tests/systemc/sc_log/golden/test01.log b/tests/systemc/sc_log/golden/test01.log new file mode 100644 index 000000000..e69de29bb diff --git a/tests/systemc/sc_log/test01.cpp b/tests/systemc/sc_log/test01.cpp new file mode 100644 index 000000000..170a30b22 --- /dev/null +++ b/tests/systemc/sc_log/test01.cpp @@ -0,0 +1,101 @@ +/***************************************************************************** + + 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. + + *****************************************************************************/ + +/***************************************************************************** + + test01.cpp -- + + Original Author: Mark Burton, Qualcomm Innovation Center, Inc. 2024 + + *****************************************************************************/ + +/***************************************************************************** + + MODIFICATION LOG - modifiers, enter your name, affiliation, date and + changes you are making here. + + Name, Affiliation, Date: + Description of Modification: + + *****************************************************************************/ + +#include "systemc.h" + +SC_MODULE(mod_a) { + SC_LOG_HANDLE((TST), "test_handler"); + SC_CTOR(mod_a) { + for (auto l : sc_log::log_level_map) { + auto i = l.first; + SC_LOG_AT(i, SCMOD) << " Log to SCMOD" << " (at level "<< i<<")"; + SC_LOG_AT(i, ()) << " Log to default ()" << " (at level "<< i<<")"; + SC_LOG_AT(i, (TST)) << " Log to test_handler" << " (at level "<< i<<")"; + } + + SC_CRITICAL(()) << "SC_CRITICAL"; + SC_WARN(()) << "SC_WARN"; + SC_INFO(()) << "SC_INFO"; + SC_DEBUG(()) << "SC_DEBUG"; + SC_TRACE(()) << "SC_TRACE"; + } +}; + +class scp_logger_test : public sc_log::sc_log_global_logger_handler { + sc_log::log_levels operator()(struct sc_log::sc_log_logger_cache &logger, + std::string_view scname, + const char *tname) const { + if (logger.features.size() && logger.features[0] == "test_handler") { + return sc_log::log_levels::INFO; + } + if (scname == "sc_log_test") { + /* test every time, and dont cache */ + return sc_log::log_levels::WARN; + } + /* Cache this one which will catch the normal SCMOD case for mod_a */ + logger.level = sc_log::log_levels::TRACE; + return sc_log::log_levels::TRACE; + } +}; +static scp_logger_test test_logger_handler; + +void report_handler(const sc_core::sc_report& rep, const sc_core::sc_actions& actions) +{ + cout << "TEST REPORT: "< Date: Tue, 22 Apr 2025 20:17:11 +0200 Subject: [PATCH 02/50] Fixes for compatibility with gcc Signed-off-by: Mark Burton --- src/sc_log/sc_log_types.h | 5 +++-- src/sysc/kernel/sc_simcontext.cpp | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sc_log/sc_log_types.h b/src/sc_log/sc_log_types.h index 349d984f8..7fabbbff1 100644 --- a/src/sc_log/sc_log_types.h +++ b/src/sc_log/sc_log_types.h @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -121,7 +122,7 @@ inline std::ostream &operator<<(std::ostream &os, log_levels const &val) { class call_sc_name_fn { template static auto test(T *p) - -> decltype(p->sc_core::sc_module::name(), std::true_type()); + -> decltype(p->name(), std::true_type()); template static auto test(...) -> decltype(std::false_type()); template @@ -132,7 +133,7 @@ class call_sc_name_fn { template auto operator()(TYPE *p) const -> std::enable_if_t, const char *> { - return p->sc_core::sc_module::name(); + return p->name(); } // define a function IF NOT the method exists diff --git a/src/sysc/kernel/sc_simcontext.cpp b/src/sysc/kernel/sc_simcontext.cpp index 065dc6bdb..667e992af 100644 --- a/src/sysc/kernel/sc_simcontext.cpp +++ b/src/sysc/kernel/sc_simcontext.cpp @@ -62,6 +62,7 @@ #include #include #include +#include // DEBUGGING MACROS: // From 5140c7c0222b58d102d09f3949b1ebfc68fd9c37 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Wed, 23 Apr 2025 14:08:53 +0200 Subject: [PATCH 03/50] Add format include to sc_log Signed-off-by: Mark Burton --- src/sc_log/sc_log.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sc_log/sc_log.h b/src/sc_log/sc_log.h index 55b9ea360..1150bbd66 100644 --- a/src/sc_log/sc_log.h +++ b/src/sc_log/sc_log.h @@ -28,6 +28,7 @@ #include #include #include +#include #include From ec10ba3f8eab5c34830b6af61127b47574cf0dbc Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Wed, 23 Apr 2025 14:09:31 +0200 Subject: [PATCH 04/50] Comment out redundent TRACE macro in risc_cpu example Signed-off-by: Mark Burton --- examples/sysc/risc_cpu/directive.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/sysc/risc_cpu/directive.h b/examples/sysc/risc_cpu/directive.h index cf1f6ae9e..9b43f6bf2 100644 --- a/examples/sysc/risc_cpu/directive.h +++ b/examples/sysc/risc_cpu/directive.h @@ -37,7 +37,7 @@ //#define DEBUG true -#define TRACE false +//#define TRACE false #define PRINT_IFU true #define PRINT_ID true #define PRINT_PU false From 15328bc0d85d3ff658250cc114e07e9c625b2679 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Wed, 23 Apr 2025 15:30:50 +0200 Subject: [PATCH 05/50] clean up comments and make guards consistent with filenames, make cache protected, remove SCMOD Signed-off-by: Mark Burton --- src/sc_log/sc_log.cpp | 6 +---- src/sc_log/sc_log.h | 38 ++++++++++++-------------- src/sc_log/sc_log_types.h | 47 +++++++++++++++------------------ src/sysc/kernel/sc_module.h | 3 ++- tests/systemc/sc_log/test01.cpp | 2 +- 5 files changed, 43 insertions(+), 53 deletions(-) diff --git a/src/sc_log/sc_log.cpp b/src/sc_log/sc_log.cpp index a115863ed..b8b3d09f7 100644 --- a/src/sc_log/sc_log.cpp +++ b/src/sc_log/sc_log.cpp @@ -14,13 +14,9 @@ * limitations under the License. *******************************************************************************/ /* - * sc_report.cpp - * * Created on: 19.09.2017 * Author: eyck@minres.com - * - * THIS FILE IS INTENDED TO BE UP-STREAMED - */ + */ #include "sc_log/sc_log_types.h" #include "sysc/kernel/sc_simcontext.h" diff --git a/src/sc_log/sc_log.h b/src/sc_log/sc_log.h index 1150bbd66..f3ff9cbb3 100644 --- a/src/sc_log/sc_log.h +++ b/src/sc_log/sc_log.h @@ -13,12 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -/* - * - * THIS FILE IS INTENDED TO BE UP-STREAMED - */ -#ifndef _SC_LOG_REPORT_H_ -#define _SC_LOG_REPORT_H_ + +#ifndef _SC_LOG_H_ +#define _SC_LOG_H_ #define SC_HAS_SC_LOG @@ -66,7 +63,6 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; #define SC_LOG_HANDLE_NAME(x) _SC_LOG_CAT(SC_LOG_LOG_LEVEL_CACHE, x) /* User interface macros */ -#define SCMOD this->sc_core::sc_module::name() #define SC_LOG_HANDLE(...) \ sc_log::sc_log_logger_cache _SC_LOG_IIF( \ _SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__)))( \ @@ -89,24 +85,24 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; // This HAS to be done as a macro, because the first argument may be a string // or a cache'd level -/*** Helper macros for SCP_ report macros ****/ -#define SCP_VBSTY_CHECK_CACHED(lvl, features, cached, ...) \ +/*** Helper macros for SC_LOG_ report macros ****/ +#define SC_LOG_VBSTY_CHECK_CACHED(lvl, features, cached, ...) \ (cached.level >= lvl) && \ (cached.get_log_verbosity_cached(sc_log::call_sc_name_fn()(this), \ typeid(*this).name()) >= lvl) -#define SCP_VBSTY_CHECK_UNCACHED(lvl, ...) \ +#define SC_LOG_VBSTY_CHECK_UNCACHED(lvl, ...) \ (::sc_log::get_log_verbosity(__VA_ARGS__) >= lvl) -#define SCP_VBSTY_CHECK(lvl, ...) \ +#define SC_LOG_VBSTY_CHECK(lvl, ...) \ _SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__))) \ - (SCP_VBSTY_CHECK_CACHED( \ + (SC_LOG_VBSTY_CHECK_CACHED( \ lvl, _SC_LOG_FIRST_ARG(__VA_ARGS__), \ SC_LOG_HANDLE_NAME( \ _SC_LOG_EXPAND(_SC_LOG_FIRST_ARG _SC_LOG_FIRST_ARG(__VA_ARGS__)))), \ - SCP_VBSTY_CHECK_UNCACHED(lvl, ##__VA_ARGS__)) + SC_LOG_VBSTY_CHECK_UNCACHED(lvl, ##__VA_ARGS__)) -#define SCP_GET_FEATURES(...) \ +#define SC_LOG_GET_FEATURES(...) \ _SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__))) \ (_SC_LOG_FIRST_ARG _SC_LOG_EXPAND((_SC_LOG_POP_ARG( \ __VA_ARGS__, \ @@ -117,16 +113,16 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; #define _SC_LOG_FMT_EMPTY_STR(...) std::format(__VA_ARGS__) -#define SCP_MSG(lvl, ...) \ - ::sc_log::ScLogger<::sc_core::SC_INFO, false>(__FILE__, __LINE__, lvl) \ - .type(SCP_GET_FEATURES(__VA_ARGS__)) \ +#define SC_LOG_MSG(lvl, ...) \ + ::sc_log::sc_logger<::sc_core::SC_INFO, false>(__FILE__, __LINE__, lvl) \ + .type(SC_LOG_GET_FEATURES(__VA_ARGS__)) \ .get() \ << _SC_LOG_FMT_EMPTY_STR /*** End HELPER Macros *******/ #define SC_LOG_AT(lvl, ...) \ - if (SCP_VBSTY_CHECK(lvl, __VA_ARGS__)) \ - SCP_MSG(lvl, __VA_ARGS__) + if (SC_LOG_VBSTY_CHECK(lvl, __VA_ARGS__)) \ + SC_LOG_MSG(lvl, __VA_ARGS__) #define SC_CRITICAL(...) SC_LOG_AT(sc_log::log_levels::CRITICAL, __VA_ARGS__) #define SC_WARN(...) SC_LOG_AT(sc_log::log_levels::WARN, __VA_ARGS__) @@ -134,5 +130,5 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; #define SC_DEBUG(...) SC_LOG_AT(sc_log::log_levels::DEBUG, __VA_ARGS__) #define SC_TRACE(...) SC_LOG_AT(sc_log::log_levels::TRACE, __VA_ARGS__) -/** @} */ // end of sc_log-report -#endif /* _SC_LOG_REPORT_H_ */ +/** @} */ // end of sc_log +#endif /* _SC_LOG_H_ */ diff --git a/src/sc_log/sc_log_types.h b/src/sc_log/sc_log_types.h index 7fabbbff1..a129ea4ab 100644 --- a/src/sc_log/sc_log_types.h +++ b/src/sc_log/sc_log_types.h @@ -13,12 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ -/* - * - * THIS FILE IS INTENDED TO BE UP-STREAMED - */ -#ifndef _SC_LOG_REPORT_TYPES_H_ -#define _SC_LOG_REPORT_TYPES_H_ + +#ifndef _SC_LOG_TYPES_H_ +#define _SC_LOG_TYPES_H_ #include #include @@ -210,46 +207,46 @@ inline log_levels get_log_verbosity(std::string const &t) { std::vector get_logging_parameters(); /** - * @struct ScLogger + * @struct sc_logger * @brief the logger class * - * The ScLogger creates a RTTI based output stream to be used similar to + * The sc_logger creates a RTTI based output stream to be used similar to * std::cout * * @tparam SEVERITY */ template -struct ScLogger { +struct sc_logger { /** - * @fn ScLogger(const char*, int, int=sc_core::SC_MEDIUM) + * @fn sc_logger(const char*, int, int=sc_core::SC_MEDIUM) * @brief * * @param file where the log entry originates * @param line number where the log entry originates * @param verbosity the log level */ - ScLogger(const char *file, int line, + sc_logger(const char *file, int line, log_levels verbosity = sc_log::log_levels::INFO) : t(nullptr), file(file), line(line), level(verbosity) {} - ScLogger() = delete; + sc_logger() = delete; - ScLogger(const ScLogger &) = delete; + sc_logger(const sc_logger &) = delete; - ScLogger(ScLogger &&) = delete; + sc_logger(sc_logger &&) = delete; - ScLogger &operator=(const ScLogger &) = delete; + sc_logger &operator=(const sc_logger &) = delete; - ScLogger &operator=(ScLogger &&) = delete; + sc_logger &operator=(sc_logger &&) = delete; /** - * @fn ~ScLogger() + * @fn ~sc_logger() * @brief the destructor generating the SystemC report * * NB a destructor should not throw an exception, here we attempt to prevent * the sc_report_handler from throwing The ScLogging interface is _ONLY_ for * logging, simulation control should happen in user code. */ - virtual ~ScLogger() noexcept(true) { + virtual ~sc_logger() noexcept(true) { auto old = sc_core::sc_report_handler::set_actions(SEVERITY); if (WITH_ACTIONS == false) { sc_core::sc_report_handler::set_actions( @@ -262,34 +259,34 @@ struct ScLogger { sc_core::sc_report_handler::set_actions(SEVERITY, old); } /** - * @fn ScLogger& type() + * @fn sc_logger& type() * @brief reset the category of the log entry * * @return reference to self for chaining */ - inline ScLogger &type() { + inline sc_logger &type() { this->t = nullptr; return *this; } /** - * @fn ScLogger& type(const char*) + * @fn sc_logger& type(const char*) * @brief set the category of the log entry * * @param t type of th elog entry * @return reference to self for chaining */ - inline ScLogger &type(char const *t) { + inline sc_logger &type(char const *t) { this->t = const_cast(t); return *this; } /** - * @fn ScLogger& type(std::string const&) + * @fn sc_logger& type(std::string const&) * @brief set the category of the log entry * * @param t type of th elog entry * @return reference to self for chaining */ - inline ScLogger &type(std::string const &t) { + inline sc_logger &type(std::string const &t) { this->t = const_cast(t.c_str()); return *this; } @@ -314,4 +311,4 @@ struct ScLogger { #define SC_LOG_LOG_LEVEL_CACHE _m_sc_log_log_level_cache_ /** @} */ // end of sc_log -#endif /* _SC_LOG_REPORT_H_ */ +#endif /* _SC_LOG_TYPES_H_ */ diff --git a/src/sysc/kernel/sc_module.h b/src/sysc/kernel/sc_module.h index aa50814b2..fe82e536c 100644 --- a/src/sysc/kernel/sc_module.h +++ b/src/sysc/kernel/sc_module.h @@ -88,8 +88,9 @@ class SC_API sc_module friend class sc_process_b; friend class sc_simcontext; friend class sc_initializer_function; -public: +protected: sc_log::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE; +public: sc_simcontext* sc_get_curr_simcontext() { return simcontext(); } diff --git a/tests/systemc/sc_log/test01.cpp b/tests/systemc/sc_log/test01.cpp index 170a30b22..5b3edbecf 100644 --- a/tests/systemc/sc_log/test01.cpp +++ b/tests/systemc/sc_log/test01.cpp @@ -42,7 +42,7 @@ SC_MODULE(mod_a) { SC_CTOR(mod_a) { for (auto l : sc_log::log_level_map) { auto i = l.first; - SC_LOG_AT(i, SCMOD) << " Log to SCMOD" << " (at level "<< i<<")"; + SC_LOG_AT(i, name()) << " Log to name()" << " (at level "<< i<<")"; SC_LOG_AT(i, ()) << " Log to default ()" << " (at level "<< i<<")"; SC_LOG_AT(i, (TST)) << " Log to test_handler" << " (at level "<< i<<")"; } From ad61cd8dd2556b32bd5e958cc1edb05f13cc698f Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Fri, 16 May 2025 19:22:22 +0200 Subject: [PATCH 06/50] Move sc_log under sc_core Signed-off-by: Mark Burton --- src/CMakeLists.txt | 5 ++-- src/sysc/kernel/sc_module.h | 2 +- src/sysc/kernel/sc_simcontext.h | 14 +++++----- src/{sc_log => sysc/log}/sc_log.cpp | 24 ++++++++--------- src/{sc_log => sysc/log}/sc_log.h | 27 +++++++++---------- src/{sc_log => sysc/log}/sc_log_types.h | 6 ++--- src/systemc | 2 +- tests/systemc/sc_log/golden/test01.log | 35 +++++++++++++++++++++++++ tests/systemc/sc_log/test01.cpp | 20 +++++++------- 9 files changed, 85 insertions(+), 50 deletions(-) rename src/{sc_log => sysc/log}/sc_log.cpp (77%) rename src/{sc_log => sysc/log}/sc_log.h (84%) rename src/{sc_log => sysc/log}/sc_log_types.h (98%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 51abb3eac..758701176 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -204,6 +204,7 @@ set(SYSTEMC_CORE_SRC sysc/kernel/sc_ver.cpp sysc/kernel/sc_wait.cpp sysc/kernel/sc_wait_cthread.cpp + sysc/log/sc_log.cpp sysc/tracing/sc_trace.cpp sysc/tracing/sc_trace_file_base.cpp sysc/tracing/sc_vcd_trace.cpp @@ -333,6 +334,8 @@ set(SYSTEMC_CORE_SRC sysc/kernel/sc_ver.h sysc/kernel/sc_wait.h sysc/kernel/sc_wait_cthread.h + sysc/log/sc_log.h + sysc/log/sc_log_types.h sysc/tracing/sc_trace.h sysc/tracing/sc_tracing_ids.h sysc/utils/sc_hash.h @@ -418,8 +421,6 @@ set(SYSTEMC_CORE_SRC sysc/packages/qt/qt.h sysc/packages/qt/qtmd.h > - # sc_log - sc_log/sc_log.cpp ) set(SYSTEMC_CORE_SRC_PRIVATE diff --git a/src/sysc/kernel/sc_module.h b/src/sysc/kernel/sc_module.h index fe82e536c..8ed7416ef 100644 --- a/src/sysc/kernel/sc_module.h +++ b/src/sysc/kernel/sc_module.h @@ -89,7 +89,7 @@ class SC_API sc_module friend class sc_simcontext; friend class sc_initializer_function; protected: - sc_log::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE; + sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE; public: sc_simcontext* sc_get_curr_simcontext() diff --git a/src/sysc/kernel/sc_simcontext.h b/src/sysc/kernel/sc_simcontext.h index ec857278c..bbc4af5d0 100644 --- a/src/sysc/kernel/sc_simcontext.h +++ b/src/sysc/kernel/sc_simcontext.h @@ -37,7 +37,7 @@ #include "sysc/kernel/sc_stage_callback_if.h" #include "sysc/utils/sc_hash.h" #include "sysc/utils/sc_pq.h" -#include "sc_log/sc_log_types.h" +#include "sysc/log/sc_log_types.h" #include "sysc/communication/sc_host_mutex.h" @@ -183,8 +183,8 @@ class SC_API sc_simcontext friend class sc_prim_channel; friend class sc_cthread_process; friend class sc_thread_process; - friend class sc_log::sc_log_global_logger_handler; - friend struct sc_log::sc_log_logger_cache; + friend class sc_core::sc_log_global_logger_handler; + friend struct sc_core::sc_log_logger_cache; friend SC_API sc_dt::uint64 sc_delta_count(); friend SC_API const std::vector& sc_get_top_level_events( const sc_simcontext* simc_p); @@ -335,18 +335,18 @@ class SC_API sc_simcontext protected: void set_log_verbosity_fn( - std::function fn) { dynamic_log_verbosity = fn; } - sc_log::log_levels get_log_verbosity(sc_log::sc_log_logger_cache &logger, + sc_core::log_levels get_log_verbosity(sc_core::sc_log_logger_cache &logger, const char *sc_name, const char *typ_name) { if (dynamic_log_verbosity) return dynamic_log_verbosity(logger, sc_name, typ_name); else - return sc_log::as_log(sc_report_handler::get_verbosity_level()); + return sc_core::as_log(sc_report_handler::get_verbosity_level()); } private: @@ -398,7 +398,7 @@ class SC_API sc_simcontext inline void set_simulation_status(sc_status status); - std::function dynamic_log_verbosity; + std::function dynamic_log_verbosity; private: diff --git a/src/sc_log/sc_log.cpp b/src/sysc/log/sc_log.cpp similarity index 77% rename from src/sc_log/sc_log.cpp rename to src/sysc/log/sc_log.cpp index b8b3d09f7..09d25cdde 100644 --- a/src/sc_log/sc_log.cpp +++ b/src/sysc/log/sc_log.cpp @@ -18,7 +18,7 @@ * Author: eyck@minres.com */ -#include "sc_log/sc_log_types.h" +#include "sysc/log/sc_log_types.h" #include "sysc/kernel/sc_simcontext.h" #include @@ -28,9 +28,9 @@ namespace { // races in the unordered_map #ifdef DISABLE_REPORT_THREAD_LOCAL -std::unordered_map lut; +std::unordered_map lut; #else -thread_local std::unordered_map lut; +thread_local std::unordered_map lut; #endif // BKDR hash algorithm @@ -45,10 +45,10 @@ auto char_hash(char const *str) -> uint64_t { } } // namespace -sc_log::log_levels -sc_log::sc_log_logger_cache::get_log_verbosity_cached(const char *scname, +sc_core::log_levels +sc_core::sc_log_logger_cache::get_log_verbosity_cached(const char *scname, const char *tname = "") { - if (level != sc_log::log_levels::UNSET) { + if (level != sc_core::log_levels::UNSET) { return level; } @@ -63,23 +63,23 @@ sc_log::sc_log_logger_cache::get_log_verbosity_cached(const char *scname, tname); } -auto sc_log::get_log_verbosity(char const *str) -> sc_log::log_levels { +auto sc_core::get_log_verbosity(char const *str) -> sc_core::log_levels { auto k = char_hash(str); auto it = lut.find(k); if (it != lut.end()) { return it->second; } - sc_log::sc_log_logger_cache tmp; + sc_core::sc_log_logger_cache tmp; lut[k] = tmp.get_log_verbosity_cached(str); return lut[k]; } -sc_log::sc_log_global_logger_handler::sc_log_global_logger_handler() { - std::function - fn = [&](sc_log::sc_log_logger_cache &logger, const char *sc_name, - const char *t_name) -> sc_log::log_levels { + fn = [&](sc_core::sc_log_logger_cache &logger, const char *sc_name, + const char *t_name) -> sc_core::log_levels { return operator()(logger, sc_name, t_name); }; ::sc_core::sc_get_curr_simcontext()->set_log_verbosity_fn(fn); diff --git a/src/sc_log/sc_log.h b/src/sysc/log/sc_log.h similarity index 84% rename from src/sc_log/sc_log.h rename to src/sysc/log/sc_log.h index f3ff9cbb3..f0ccb35f6 100644 --- a/src/sc_log/sc_log.h +++ b/src/sysc/log/sc_log.h @@ -27,7 +27,7 @@ #include #include -#include +#include // must be global for macro to work. static const char *_SC_LOG_FMT_EMPTY_STR = ""; @@ -64,21 +64,21 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; /* User interface macros */ #define SC_LOG_HANDLE(...) \ - sc_log::sc_log_logger_cache _SC_LOG_IIF( \ + sc_core::sc_log_logger_cache _SC_LOG_IIF( \ _SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__)))( \ SC_LOG_HANDLE_NAME( \ _SC_LOG_EXPAND(_SC_LOG_FIRST_ARG _SC_LOG_FIRST_ARG(__VA_ARGS__))), \ SC_LOG_HANDLE_NAME()) = { \ - sc_log::log_levels::UNSET, \ + sc_core::log_levels::UNSET, \ "", \ {_SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__)))( \ _SC_LOG_POP_ARG(__VA_ARGS__), ##__VA_ARGS__)}} #define SC_LOG_HANDLE_VECTOR(NAME) \ - std::vector SC_LOG_HANDLE_NAME(NAME) + std::vector SC_LOG_HANDLE_NAME(NAME) #define SC_LOG_HANDLE_VECTOR_PUSH_BACK(NAME, ...) \ SC_LOG_HANDLE_NAME(NAME).push_back( \ - {sc_log::log_levels::UNSET, "", {__VA_ARGS__}}); + {sc_core::log_levels::UNSET, "", {__VA_ARGS__}}); // critical thing is that the initial if 'fails' as soon as possible - if it is // going to pass, we have all the time we want, as we will be logging anyway @@ -88,11 +88,11 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; /*** Helper macros for SC_LOG_ report macros ****/ #define SC_LOG_VBSTY_CHECK_CACHED(lvl, features, cached, ...) \ (cached.level >= lvl) && \ - (cached.get_log_verbosity_cached(sc_log::call_sc_name_fn()(this), \ + (cached.get_log_verbosity_cached(sc_core::call_sc_name_fn()(this), \ typeid(*this).name()) >= lvl) #define SC_LOG_VBSTY_CHECK_UNCACHED(lvl, ...) \ - (::sc_log::get_log_verbosity(__VA_ARGS__) >= lvl) + (::sc_core::get_log_verbosity(__VA_ARGS__) >= lvl) #define SC_LOG_VBSTY_CHECK(lvl, ...) \ _SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__))) \ @@ -114,7 +114,7 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; #define _SC_LOG_FMT_EMPTY_STR(...) std::format(__VA_ARGS__) #define SC_LOG_MSG(lvl, ...) \ - ::sc_log::sc_logger<::sc_core::SC_INFO, false>(__FILE__, __LINE__, lvl) \ + ::sc_core::sc_logger<::sc_core::SC_INFO, false>(__FILE__, __LINE__, lvl) \ .type(SC_LOG_GET_FEATURES(__VA_ARGS__)) \ .get() \ << _SC_LOG_FMT_EMPTY_STR @@ -124,11 +124,10 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; if (SC_LOG_VBSTY_CHECK(lvl, __VA_ARGS__)) \ SC_LOG_MSG(lvl, __VA_ARGS__) -#define SC_CRITICAL(...) SC_LOG_AT(sc_log::log_levels::CRITICAL, __VA_ARGS__) -#define SC_WARN(...) SC_LOG_AT(sc_log::log_levels::WARN, __VA_ARGS__) -#define SC_INFO(...) SC_LOG_AT(sc_log::log_levels::INFO, __VA_ARGS__) -#define SC_DEBUG(...) SC_LOG_AT(sc_log::log_levels::DEBUG, __VA_ARGS__) -#define SC_TRACE(...) SC_LOG_AT(sc_log::log_levels::TRACE, __VA_ARGS__) +#define SC_CRITICAL(...) SC_LOG_AT(sc_core::log_levels::CRITICAL, __VA_ARGS__) +#define SC_WARN(...) SC_LOG_AT(sc_core::log_levels::WARN, __VA_ARGS__) +#define SC_INFO(...) SC_LOG_AT(sc_core::log_levels::INFO, __VA_ARGS__) +#define SC_DEBUG(...) SC_LOG_AT(sc_core::log_levels::DEBUG, __VA_ARGS__) +#define SC_TRACE(...) SC_LOG_AT(sc_core::log_levels::TRACE, __VA_ARGS__) -/** @} */ // end of sc_log #endif /* _SC_LOG_H_ */ diff --git a/src/sc_log/sc_log_types.h b/src/sysc/log/sc_log_types.h similarity index 98% rename from src/sc_log/sc_log_types.h rename to src/sysc/log/sc_log_types.h index a129ea4ab..1acbeff86 100644 --- a/src/sc_log/sc_log_types.h +++ b/src/sysc/log/sc_log_types.h @@ -32,7 +32,7 @@ */ /**@{*/ //! @brief Logging utilities -namespace sc_log { +namespace sc_core { /************************ * Provide a set of names and conversions that are suitable for logging levels @@ -226,7 +226,7 @@ struct sc_logger { * @param verbosity the log level */ sc_logger(const char *file, int line, - log_levels verbosity = sc_log::log_levels::INFO) + log_levels verbosity = sc_core::log_levels::INFO) : t(nullptr), file(file), line(line), level(verbosity) {} sc_logger() = delete; @@ -306,7 +306,7 @@ struct sc_logger { const log_levels level; }; -} // namespace sc_log +} // namespace sc_core #define SC_LOG_LOG_LEVEL_CACHE _m_sc_log_log_level_cache_ diff --git a/src/systemc b/src/systemc index a599aff51..a30e05a0c 100644 --- a/src/systemc +++ b/src/systemc @@ -134,7 +134,7 @@ #include "sysc/utils/sc_vector.h" #include "sysc/utils/sc_string.h" -#include "sc_log/sc_log.h" +#include "sysc/log/sc_log.h" #endif // !defined(SYSTEMC_INCLUDED) diff --git a/tests/systemc/sc_log/golden/test01.log b/tests/systemc/sc_log/golden/test01.log index e69de29bb..134c7f64b 100644 --- a/tests/systemc/sc_log/golden/test01.log +++ b/tests/systemc/sc_log/golden/test01.log @@ -0,0 +1,35 @@ +SystemC Simulation +0 is log_level CRITICAL +50 is log_level WARN +100 is log_level WARN +150 is log_level INFO +200 is log_level INFO +250 is log_level DEBUG +300 is log_level DEBUG +350 is log_level TRACE +400 is log_level TRACE +450 is log_level TRACE +Test string based handler +TEST REPORT: CRITICAL : [sc_log_test] CRITICAL +TEST REPORT: WARN : [sc_log_test] WARN +test FMT string +TEST REPORT: WARN : [SystemC] Testing FMT Hello world +construct module +TEST REPORT: CRITICAL : [MyMod] Log to name() (at level CRITICAL) +TEST REPORT: CRITICAL : [MyMod] Log to default () (at level CRITICAL) +TEST REPORT: CRITICAL : [MyMod] Log to test_handler (at level CRITICAL) +TEST REPORT: WARN : [MyMod] Log to name() (at level WARN) +TEST REPORT: WARN : [MyMod] Log to default () (at level WARN) +TEST REPORT: WARN : [MyMod] Log to test_handler (at level WARN) +TEST REPORT: INFO : [MyMod] Log to name() (at level INFO) +TEST REPORT: INFO : [MyMod] Log to default () (at level INFO) +TEST REPORT: INFO : [MyMod] Log to test_handler (at level INFO) +TEST REPORT: DEBUG : [MyMod] Log to name() (at level DEBUG) +TEST REPORT: DEBUG : [MyMod] Log to default () (at level DEBUG) +TEST REPORT: TRACE : [MyMod] Log to name() (at level TRACE) +TEST REPORT: TRACE : [MyMod] Log to default () (at level TRACE) +TEST REPORT: CRITICAL : [MyMod] SC_CRITICAL +TEST REPORT: WARN : [MyMod] SC_WARN +TEST REPORT: INFO : [MyMod] SC_INFO +TEST REPORT: DEBUG : [MyMod] SC_DEBUG +TEST REPORT: TRACE : [MyMod] SC_TRACE diff --git a/tests/systemc/sc_log/test01.cpp b/tests/systemc/sc_log/test01.cpp index 5b3edbecf..547e0a5dc 100644 --- a/tests/systemc/sc_log/test01.cpp +++ b/tests/systemc/sc_log/test01.cpp @@ -40,7 +40,7 @@ SC_MODULE(mod_a) { SC_LOG_HANDLE((TST), "test_handler"); SC_CTOR(mod_a) { - for (auto l : sc_log::log_level_map) { + for (auto l : sc_core::log_level_map) { auto i = l.first; SC_LOG_AT(i, name()) << " Log to name()" << " (at level "<< i<<")"; SC_LOG_AT(i, ()) << " Log to default ()" << " (at level "<< i<<")"; @@ -55,27 +55,27 @@ SC_MODULE(mod_a) { } }; -class scp_logger_test : public sc_log::sc_log_global_logger_handler { - sc_log::log_levels operator()(struct sc_log::sc_log_logger_cache &logger, +class scp_logger_test : public sc_core::sc_log_global_logger_handler { + sc_core::log_levels operator()(struct sc_core::sc_log_logger_cache &logger, std::string_view scname, const char *tname) const { if (logger.features.size() && logger.features[0] == "test_handler") { - return sc_log::log_levels::INFO; + return sc_core::log_levels::INFO; } if (scname == "sc_log_test") { /* test every time, and dont cache */ - return sc_log::log_levels::WARN; + return sc_core::log_levels::WARN; } /* Cache this one which will catch the normal SCMOD case for mod_a */ - logger.level = sc_log::log_levels::TRACE; - return sc_log::log_levels::TRACE; + logger.level = sc_core::log_levels::TRACE; + return sc_core::log_levels::TRACE; } }; static scp_logger_test test_logger_handler; void report_handler(const sc_core::sc_report& rep, const sc_core::sc_actions& actions) { - cout << "TEST REPORT: "< Date: Tue, 30 Sep 2025 14:47:34 +0200 Subject: [PATCH 07/50] fixes licensing header in sc_log.h --- src/sysc/log/sc_log.h | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/sysc/log/sc_log.h b/src/sysc/log/sc_log.h index f0ccb35f6..4a1f235df 100644 --- a/src/sysc/log/sc_log.h +++ b/src/sysc/log/sc_log.h @@ -1,18 +1,24 @@ -/******************************************************************************* - * Copyright 2016-2022 MINRES Technologies GmbH - * - * Licensed 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. - *******************************************************************************/ +/***************************************************************************** + 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. + *****************************************************************************/ +/***************************************************************************** + sc_log.h --SystemC logging functions. + Original Author: Eyck Jentzsch, MINRES Technologies GmbH + Mark Burton, Qualcomm Technologies, Inc. + + CHANGE LOG AT THE END OF THE FILE + *****************************************************************************/ #ifndef _SC_LOG_H_ #define _SC_LOG_H_ From e03c79afc131da6d2f796f30e89ade342e8e65ce Mon Sep 17 00:00:00 2001 From: Eyck Jentzsch Date: Tue, 30 Sep 2025 14:48:07 +0200 Subject: [PATCH 08/50] fixes licensing header of sc_log.cpp --- src/sysc/log/sc_log.cpp | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/sysc/log/sc_log.cpp b/src/sysc/log/sc_log.cpp index 09d25cdde..c74aa2f0f 100644 --- a/src/sysc/log/sc_log.cpp +++ b/src/sysc/log/sc_log.cpp @@ -1,22 +1,25 @@ -/******************************************************************************* - * Copyright 2017-2022 MINRES Technologies GmbH - * - * Licensed 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. - *******************************************************************************/ -/* - * Created on: 19.09.2017 - * Author: eyck@minres.com - */ +/***************************************************************************** + 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. + *****************************************************************************/ +/***************************************************************************** + sc_log.h --SystemC logging functions. + Original Author: Eyck Jentzsch, MINRES Technologies GmbH + Mark Burton, Qualcomm Technologies, Inc. + + CHANGE LOG AT THE END OF THE FILE + *****************************************************************************/ + #include "sysc/log/sc_log_types.h" #include "sysc/kernel/sc_simcontext.h" From f2b0cf040aee65fd064cf2a422b7790f3867d08f Mon Sep 17 00:00:00 2001 From: Eyck Jentzsch Date: Tue, 30 Sep 2025 19:43:21 +0200 Subject: [PATCH 09/50] updates sc_log_types.h license header --- src/sysc/log/sc_log_types.h | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/sysc/log/sc_log_types.h b/src/sysc/log/sc_log_types.h index 1acbeff86..fb73c2b01 100644 --- a/src/sysc/log/sc_log_types.h +++ b/src/sysc/log/sc_log_types.h @@ -1,18 +1,24 @@ -/******************************************************************************* - * Copyright 2016-2022 MINRES Technologies GmbH - * - * Licensed 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. - *******************************************************************************/ +/***************************************************************************** + 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. + *****************************************************************************/ +/***************************************************************************** + sc_log.h --SystemC logging functions. + Original Author: Eyck Jentzsch, MINRES Technologies GmbH + Mark Burton, Qualcomm Technologies, Inc. + + CHANGE LOG AT THE END OF THE FILE + *****************************************************************************/ #ifndef _SC_LOG_TYPES_H_ #define _SC_LOG_TYPES_H_ From ff0216ddcdcb0488738c3eb4608ae1ad32f5cc81 Mon Sep 17 00:00:00 2001 From: Eyck Jentzsch Date: Tue, 30 Sep 2025 19:46:52 +0200 Subject: [PATCH 10/50] adds MINRES Technologies to the list of authors --- NOTICE | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NOTICE b/NOTICE index 8077e3578..9e3f7a60e 100644 --- a/NOTICE +++ b/NOTICE @@ -68,6 +68,11 @@ This product includes software developed by Mentor Graphics Corporation Copyright 2006-2019 Mentor Graphics Corporation All rights reserved. +This product includes software developed by MINRES Technologies GmbH +Keltenhof 2, 85579 Neubibergm Germany +Copyright 2016-2025 XtremeEDA Corporation. +All rights reserved. + This product includes software developed by NXP B.V. High Tech Campus 60, 5656 AG Eindhoven, Netherlands Copyright 2012-2023 NXP B.V. From a99141a4b736dde142f3f14b7fc27fcbed20763a Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Mon, 19 Jan 2026 09:45:25 +0100 Subject: [PATCH 11/50] Rename macros to be compliant, and remove log handler convenience class Signed-off-by: Mark Burton --- src/sysc/kernel/sc_kernel_ids.h | 2 ++ src/sysc/kernel/sc_simcontext.h | 8 +++-- src/sysc/log/sc_log.h | 56 ++++++++++++++++----------------- src/sysc/log/sc_log_types.h | 6 ++-- tests/systemc/sc_log/test01.cpp | 32 +++++++++++++++++-- 5 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/sysc/kernel/sc_kernel_ids.h b/src/sysc/kernel/sc_kernel_ids.h index 1ed1ea94d..93b5c52dc 100644 --- a/src/sysc/kernel/sc_kernel_ids.h +++ b/src/sysc/kernel/sc_kernel_ids.h @@ -208,6 +208,8 @@ SC_DEFINE_MESSAGE(SC_ID_UNSUSPENDABLE_NOTHREAD_ , 577, "unsuspendable/suspendable only valid inside a process" ) SC_DEFINE_MESSAGE(SC_ID_UNBALANCED_UNSUSPENDALL_ , 578, "Unmatched unsuspendall/suspendall" ) +SC_DEFINE_MESSAGE(SC_LOG_OVERWRITE_VERBOSITY_FN_, 579, + "Verbosity function already set - ignoring subsequent calls to set_log_verbosity_fn" ) /***************************************************************************** diff --git a/src/sysc/kernel/sc_simcontext.h b/src/sysc/kernel/sc_simcontext.h index bbc4af5d0..259200def 100644 --- a/src/sysc/kernel/sc_simcontext.h +++ b/src/sysc/kernel/sc_simcontext.h @@ -38,6 +38,7 @@ #include "sysc/utils/sc_hash.h" #include "sysc/utils/sc_pq.h" #include "sysc/log/sc_log_types.h" +#include "sysc/utils/sc_utils_ids.h" #include "sysc/communication/sc_host_mutex.h" @@ -333,12 +334,15 @@ class SC_API sc_simcontext void pre_suspend() const; void post_suspend() const; - protected: void set_log_verbosity_fn( std::function fn) { - dynamic_log_verbosity = fn; + if (dynamic_log_verbosity) { + SC_REPORT_WARNING(SC_LOG_OVERWRITE_VERBOSITY_FN_, 0); + } else { + dynamic_log_verbosity = fn; + } } sc_core::log_levels get_log_verbosity(sc_core::sc_log_logger_cache &logger, const char *sc_name, diff --git a/src/sysc/log/sc_log.h b/src/sysc/log/sc_log.h index 4a1f235df..6b630f13b 100644 --- a/src/sysc/log/sc_log.h +++ b/src/sysc/log/sc_log.h @@ -36,7 +36,7 @@ #include // must be global for macro to work. -static const char *_SC_LOG_FMT_EMPTY_STR = ""; +static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; /** * logging macros @@ -45,40 +45,40 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; /** * Boilerplate convenience macros */ -#define _SC_LOG_CAT(a, ...) _SC_LOG_PRIMITIVE_CAT(a, __VA_ARGS__) -#define _SC_LOG_PRIMITIVE_CAT(a, ...) a##__VA_ARGS__ +#define SC_LOG_PRIV__CAT(a, ...) SC_LOG_PRIV__PRIMITIVE_CAT(a, __VA_ARGS__) +#define SC_LOG_PRIV__PRIMITIVE_CAT(a, ...) a##__VA_ARGS__ -#define _SC_LOG_IIF(c) _SC_LOG_PRIMITIVE_CAT(_SC_LOG_IIF_, c) -#define _SC_LOG_IIF_0(t, ...) __VA_ARGS__ -#define _SC_LOG_IIF_1(t, ...) t +#define SC_LOG_PRIV__IIF(c) SC_LOG_PRIV__PRIMITIVE_CAT(SC_LOG_PRIV__IIF_, c) +#define SC_LOG_PRIV__IIF_0(t, ...) __VA_ARGS__ +#define SC_LOG_PRIV__IIF_1(t, ...) t -#define _SC_LOG_CHECK_N(x, n, ...) n -#define _SC_LOG_CHECK(...) _SC_LOG_CHECK_N(__VA_ARGS__, 0, ) -#define _SC_LOG_PROBE(x) x, 1, +#define SC_LOG_PRIV__CHECK_N(x, n, ...) n +#define SC_LOG_PRIV__CHECK(...) SC_LOG_PRIV__CHECK_N(__VA_ARGS__, 0, ) +#define SC_LOG_PRIV__PROBE(x) x, 1, -#define _SC_LOG_EXPAND(...) __VA_ARGS__ +#define SC_LOG_PRIV__EXPAND(...) __VA_ARGS__ -#define _SC_LOG_FIRST_ARG(f, ...) f -#define _SC_LOG_POP_ARG(f, ...) __VA_ARGS__ +#define SC_LOG_PRIV__FIRST_ARG(f, ...) f +#define SC_LOG_PRIV__POP_ARG(f, ...) __VA_ARGS__ -#define _SC_LOG_IS_PAREN(x) _SC_LOG_CHECK(_SC_LOG_IS_PAREN_PROBE x) -#define _SC_LOG_IS_PAREN_PROBE(...) _SC_LOG_PROBE(~) +#define SC_LOG_PRIV__IS_PAREN(x) SC_LOG_PRIV__CHECK(SC_LOG_PRIV__IS_PAREN_PROBE x) +#define SC_LOG_PRIV__IS_PAREN_PROBE(...) SC_LOG_PRIV__PROBE(~) /********/ /* default logger cache name */ -#define SC_LOG_HANDLE_NAME(x) _SC_LOG_CAT(SC_LOG_LOG_LEVEL_CACHE, x) +#define SC_LOG_HANDLE_NAME(x) SC_LOG_PRIV__CAT(SC_LOG_LOG_LEVEL_CACHE, x) /* User interface macros */ #define SC_LOG_HANDLE(...) \ - sc_core::sc_log_logger_cache _SC_LOG_IIF( \ - _SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__)))( \ + sc_core::sc_log_logger_cache SC_LOG_PRIV__IIF( \ + SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__)))( \ SC_LOG_HANDLE_NAME( \ - _SC_LOG_EXPAND(_SC_LOG_FIRST_ARG _SC_LOG_FIRST_ARG(__VA_ARGS__))), \ + SC_LOG_PRIV__EXPAND(SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))), \ SC_LOG_HANDLE_NAME()) = { \ sc_core::log_levels::UNSET, \ "", \ - {_SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__)))( \ - _SC_LOG_POP_ARG(__VA_ARGS__), ##__VA_ARGS__)}} + {SC_LOG_PRIV__IIF(SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__)))( \ + SC_LOG_PRIV__POP_ARG(__VA_ARGS__), ##__VA_ARGS__)}} #define SC_LOG_HANDLE_VECTOR(NAME) \ std::vector SC_LOG_HANDLE_NAME(NAME) @@ -101,29 +101,29 @@ static const char *_SC_LOG_FMT_EMPTY_STR = ""; (::sc_core::get_log_verbosity(__VA_ARGS__) >= lvl) #define SC_LOG_VBSTY_CHECK(lvl, ...) \ - _SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__))) \ + SC_LOG_PRIV__IIF(SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))) \ (SC_LOG_VBSTY_CHECK_CACHED( \ - lvl, _SC_LOG_FIRST_ARG(__VA_ARGS__), \ + lvl, SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__), \ SC_LOG_HANDLE_NAME( \ - _SC_LOG_EXPAND(_SC_LOG_FIRST_ARG _SC_LOG_FIRST_ARG(__VA_ARGS__)))), \ + SC_LOG_PRIV__EXPAND(SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__)))), \ SC_LOG_VBSTY_CHECK_UNCACHED(lvl, ##__VA_ARGS__)) #define SC_LOG_GET_FEATURES(...) \ - _SC_LOG_IIF(_SC_LOG_IS_PAREN(_SC_LOG_FIRST_ARG(__VA_ARGS__))) \ - (_SC_LOG_FIRST_ARG _SC_LOG_EXPAND((_SC_LOG_POP_ARG( \ + SC_LOG_PRIV__IIF(SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))) \ + (SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__EXPAND((SC_LOG_PRIV__POP_ARG( \ __VA_ARGS__, \ SC_LOG_HANDLE_NAME( \ - _SC_LOG_EXPAND(_SC_LOG_FIRST_ARG _SC_LOG_FIRST_ARG(__VA_ARGS__))) \ + SC_LOG_PRIV__EXPAND(SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))) \ .type))), \ __VA_ARGS__) -#define _SC_LOG_FMT_EMPTY_STR(...) std::format(__VA_ARGS__) +#define SC_LOG_PRIV__FMT_EMPTY_STR(...) std::format(__VA_ARGS__) #define SC_LOG_MSG(lvl, ...) \ ::sc_core::sc_logger<::sc_core::SC_INFO, false>(__FILE__, __LINE__, lvl) \ .type(SC_LOG_GET_FEATURES(__VA_ARGS__)) \ .get() \ - << _SC_LOG_FMT_EMPTY_STR + << SC_LOG_PRIV__FMT_EMPTY_STR /*** End HELPER Macros *******/ #define SC_LOG_AT(lvl, ...) \ diff --git a/src/sysc/log/sc_log_types.h b/src/sysc/log/sc_log_types.h index fb73c2b01..f1b63f384 100644 --- a/src/sysc/log/sc_log_types.h +++ b/src/sysc/log/sc_log_types.h @@ -121,7 +121,7 @@ inline std::ostream &operator<<(std::ostream &os, log_levels const &val) { return os; } -/* Convenience function to allow useage outside of SystemC heirarchy */ +/* Convenience function to allow useage outside of SystemC hierarchy */ class call_sc_name_fn { template static auto test(T *p) @@ -278,7 +278,7 @@ struct sc_logger { * @fn sc_logger& type(const char*) * @brief set the category of the log entry * - * @param t type of th elog entry + * @param t type of the log entry * @return reference to self for chaining */ inline sc_logger &type(char const *t) { @@ -289,7 +289,7 @@ struct sc_logger { * @fn sc_logger& type(std::string const&) * @brief set the category of the log entry * - * @param t type of th elog entry + * @param t type of the log entry * @return reference to self for chaining */ inline sc_logger &type(std::string const &t) { diff --git a/tests/systemc/sc_log/test01.cpp b/tests/systemc/sc_log/test01.cpp index 547e0a5dc..2e06b779a 100644 --- a/tests/systemc/sc_log/test01.cpp +++ b/tests/systemc/sc_log/test01.cpp @@ -36,6 +36,7 @@ *****************************************************************************/ #include "systemc.h" +#include SC_MODULE(mod_a) { SC_LOG_HANDLE((TST), "test_handler"); @@ -55,10 +56,16 @@ SC_MODULE(mod_a) { } }; -class scp_logger_test : public sc_core::sc_log_global_logger_handler { + /* This is an example of how one could construct a class around the basic "set_log_verbosity_fn" API + * In doing so, a tool could construct extra functionality (like resetting cached values) + * But this is not part of the standard, and tool environments might differ. + */ +class scp_logger_test { + std::unordered_set loggers; sc_core::log_levels operator()(struct sc_core::sc_log_logger_cache &logger, std::string_view scname, - const char *tname) const { + const char *tname) { + loggers.insert(&logger); if (logger.features.size() && logger.features[0] == "test_handler") { return sc_core::log_levels::INFO; } @@ -70,7 +77,28 @@ class scp_logger_test : public sc_core::sc_log_global_logger_handler { logger.level = sc_core::log_levels::TRACE; return sc_core::log_levels::TRACE; } +public: + scp_logger_test() { + std::function + fn = [&](sc_core::sc_log_logger_cache &logger, const char *sc_name, + const char *t_name) -> sc_core::log_levels { + return operator()(logger, sc_name, t_name); + }; + ::sc_core::sc_get_curr_simcontext()->set_log_verbosity_fn(fn); + ::sc_core::sc_report_handler::set_verbosity_level( + sc_core::SC_DEBUG); // Set the level in the core to DEBUG such that the + // handler can manage all levels of verbosity + } + void reset() { + for (auto *logger : loggers) { + if (logger) { + logger->level = sc_core::log_levels::UNSET; + } + } + } }; + static scp_logger_test test_logger_handler; void report_handler(const sc_core::sc_report& rep, const sc_core::sc_actions& actions) From 89f4acbc0cd3a4d05e192e5edb1a9e6374d42617 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Mon, 19 Jan 2026 16:05:10 +0100 Subject: [PATCH 12/50] Previx name of call_sc_name_fn convenience class with sc_log_priv_ Signed-off-by: Mark Burton --- src/sysc/log/sc_log.h | 2 +- src/sysc/log/sc_log_types.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sysc/log/sc_log.h b/src/sysc/log/sc_log.h index 6b630f13b..68fc459af 100644 --- a/src/sysc/log/sc_log.h +++ b/src/sysc/log/sc_log.h @@ -94,7 +94,7 @@ static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; /*** Helper macros for SC_LOG_ report macros ****/ #define SC_LOG_VBSTY_CHECK_CACHED(lvl, features, cached, ...) \ (cached.level >= lvl) && \ - (cached.get_log_verbosity_cached(sc_core::call_sc_name_fn()(this), \ + (cached.get_log_verbosity_cached(sc_core::sc_log_priv__call_sc_name_fn()(this), \ typeid(*this).name()) >= lvl) #define SC_LOG_VBSTY_CHECK_UNCACHED(lvl, ...) \ diff --git a/src/sysc/log/sc_log_types.h b/src/sysc/log/sc_log_types.h index f1b63f384..73484c1ed 100644 --- a/src/sysc/log/sc_log_types.h +++ b/src/sysc/log/sc_log_types.h @@ -122,7 +122,7 @@ inline std::ostream &operator<<(std::ostream &os, log_levels const &val) { } /* Convenience function to allow useage outside of SystemC hierarchy */ -class call_sc_name_fn { +class sc_log_priv__call_sc_name_fn { template static auto test(T *p) -> decltype(p->name(), std::true_type()); From c138522e7ba080423f15392472d61aff53824d38 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Fri, 30 Jan 2026 15:16:21 +0100 Subject: [PATCH 13/50] Expose the dynamic logging mechanism as an implementation defined API, and document it in the example Signed-off-by: Mark Burton --- src/sysc/kernel/sc_simcontext.cpp | 37 ++++++++++++++++++ src/sysc/kernel/sc_simcontext.h | 48 +++++++++++++++-------- src/sysc/log/sc_log.cpp | 5 +-- tests/systemc/sc_log/test01.cpp | 65 +++++++++++++++++++++++++++++-- 4 files changed, 132 insertions(+), 23 deletions(-) diff --git a/src/sysc/kernel/sc_simcontext.cpp b/src/sysc/kernel/sc_simcontext.cpp index 667e992af..8b0214912 100644 --- a/src/sysc/kernel/sc_simcontext.cpp +++ b/src/sysc/kernel/sc_simcontext.cpp @@ -2072,6 +2072,43 @@ SC_API void sc_unregister_stage_callback(sc_stage_callback_if & cb, ->unregister_callback(cb, mask); } +// +// Implementation defined Dynamic log verbosity dispatch +// +void sc_simcontext::set_log_verbosity_fn( + std::function fn) +{ + if (dynamic_log_verbosity) { + SC_REPORT_WARNING(SC_LOG_OVERWRITE_VERBOSITY_FN_, 0); + } else { + dynamic_log_verbosity = std::move(fn); + } +} + +log_levels sc_simcontext::get_log_verbosity(sc_log_logger_cache &logger, + const char *sc_name, + const char *typ_name) +{ + if (dynamic_log_verbosity) + return dynamic_log_verbosity(logger, sc_name, typ_name); + else + return as_log(sc_report_handler::get_verbosity_level()); +} + +// NB these functions are NOT a standard API, but are +// exposed to provide access to the (implementation defined) dynamic logging configuration +void sc_set_log_verbosity_fn( + std::function fn) +{ + sc_get_curr_simcontext()->set_log_verbosity_fn(std::move(fn)); +} +log_levels sc_get_log_verbosity(sc_log_logger_cache &logger, + const char *sc_name, + const char *typ_name) +{ + return sc_get_curr_simcontext()->get_log_verbosity(logger, sc_name, typ_name); +} + } // namespace sc_core /***************************************************************************** diff --git a/src/sysc/kernel/sc_simcontext.h b/src/sysc/kernel/sc_simcontext.h index 259200def..4e2d95772 100644 --- a/src/sysc/kernel/sc_simcontext.h +++ b/src/sysc/kernel/sc_simcontext.h @@ -150,6 +150,25 @@ SC_API void sc_register_stage_callback(sc_stage_callback_if & cb, SC_API void sc_unregister_stage_callback(sc_stage_callback_if & cb, unsigned int mask); +// +------------------------------------------------------------------------------------------------ +// | Non standard API: Install a callback used to determine the effective log level for a given +// | (sc_name, typ_name) pair. The cache parameter may be used to remember +// | a computed level, once set the function will not be re-called. +// +------------------------------------------------------------------------------------------------ +void sc_set_log_verbosity_fn( + std::function fn); + +// +------------------------------------------------------------------------------------------------ +// | Non standard API: Query the current log verbosity for the given handle cache and identifiers. +// | If no callback has been installed, the implementation shall fall back to +// | the global report verbosity. +// +------------------------------------------------------------------------------------------------ +sc_core::log_levels sc_get_log_verbosity( + sc_core::sc_log_logger_cache &logger, + const char *sc_name, + const char *typ_name); + class sc_invoke_method; SC_API void sc_suspend_all(); @@ -210,6 +229,14 @@ class SC_API sc_simcontext friend SC_API void sc_unregister_stage_callback(sc_stage_callback_if & cb, unsigned int mask); + friend void sc_set_log_verbosity_fn( + std::function fn); + + friend sc_core::log_levels + sc_get_log_verbosity(sc_core::sc_log_logger_cache &logger, + const char *sc_name, const char *typ_name); + enum sc_signal_write_check { SC_SIGNAL_WRITE_CHECK_DISABLE_ = 0x0, // no multiple writer checks @@ -336,22 +363,11 @@ class SC_API sc_simcontext void set_log_verbosity_fn( std::function - fn) { - if (dynamic_log_verbosity) { - SC_REPORT_WARNING(SC_LOG_OVERWRITE_VERBOSITY_FN_, 0); - } else { - dynamic_log_verbosity = fn; - } - } - sc_core::log_levels get_log_verbosity(sc_core::sc_log_logger_cache &logger, - const char *sc_name, - const char *typ_name) { - if (dynamic_log_verbosity) - return dynamic_log_verbosity(logger, sc_name, typ_name); - else - return sc_core::as_log(sc_report_handler::get_verbosity_level()); - } + const char *, const char *)> fn); + sc_core::log_levels get_log_verbosity( + sc_core::sc_log_logger_cache &logger, + const char *sc_name, + const char *typ_name); private: void hierarchy_push(sc_object_host*); diff --git a/src/sysc/log/sc_log.cpp b/src/sysc/log/sc_log.cpp index c74aa2f0f..3b65f3956 100644 --- a/src/sysc/log/sc_log.cpp +++ b/src/sysc/log/sc_log.cpp @@ -62,8 +62,7 @@ sc_core::sc_log_logger_cache::get_log_verbosity_cached(const char *scname, type = std::string(scname); - return sc_core::sc_get_curr_simcontext()->get_log_verbosity(*this, scname, - tname); + return sc_core::sc_get_log_verbosity(*this, scname, tname); } auto sc_core::get_log_verbosity(char const *str) -> sc_core::log_levels { @@ -85,7 +84,7 @@ sc_core::sc_log_global_logger_handler::sc_log_global_logger_handler() { const char *t_name) -> sc_core::log_levels { return operator()(logger, sc_name, t_name); }; - ::sc_core::sc_get_curr_simcontext()->set_log_verbosity_fn(fn); + ::sc_core::sc_set_log_verbosity_fn(fn); ::sc_core::sc_report_handler::set_verbosity_level( sc_core::SC_DEBUG); // Set the level in the core to DEBUG such that the // handler can manage all levels of verbosity diff --git a/tests/systemc/sc_log/test01.cpp b/tests/systemc/sc_log/test01.cpp index 2e06b779a..c85fcc54e 100644 --- a/tests/systemc/sc_log/test01.cpp +++ b/tests/systemc/sc_log/test01.cpp @@ -56,10 +56,67 @@ SC_MODULE(mod_a) { } }; - /* This is an example of how one could construct a class around the basic "set_log_verbosity_fn" API - * In doing so, a tool could construct extra functionality (like resetting cached values) - * But this is not part of the standard, and tool environments might differ. - */ + +/********************************************************** + * The mechanism by which SC_LOG macros are enabled and + * disabled is implementation-defined. + * + * The Accellera POC provides functions and a class for this + * purpose. + * + * -------------------------------------------------------- + * Non-standard API: + * + * Install a callback used to determine the effective log + * level for a given (sc_name, typ_name) pair. + * + * The cache parameter may be used to remember a computed + * level. Once set, the function will not be re-called. + * -------------------------------------------------------- + * + * void sc_set_log_verbosity_fn( + * std::function< + * sc_core::log_levels( + * sc_core::sc_log_logger_cache&, + * const char*, + * const char* + * ) + * > fn + * ); + * + * -------------------------------------------------------- + * Non-standard API: + * + * Query the current log verbosity for the given cache and + * identifiers. + * + * If no callback has been installed, the implementation + * shall fall back to the global report verbosity. + * -------------------------------------------------------- + * + * sc_core::log_levels sc_get_log_verbosity( + * sc_core::sc_log_logger_cache &logger, + * const char *sc_name, + * const char *typ_name); + * ); + * + * Together with the sc_log_logger_cache + * class, this is used by the SC_LOG macros to determine + * whether specific loggers should be enabled. + **********************************************************/ + +/********************************************************** + * Example: + * + * This demonstrates how one could construct a class around + * the basic set_log_verbosity_fn API. + * + * A tool could use this to build additional functionality + * (e.g. resetting cached values). This behavior is not + * part of the standard, and tool environments may differ. + **********************************************************/ + + class scp_logger_test { std::unordered_set loggers; sc_core::log_levels operator()(struct sc_core::sc_log_logger_cache &logger, From 176f9268996c69c3e386fc8bdac878eab6c59ca2 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 5 Feb 2026 19:35:21 +0100 Subject: [PATCH 14/50] Add FILE and LINE to SC_LOG verbosity API Signed-off-by: Mark Burton --- src/sysc/kernel/sc_simcontext.cpp | 26 ++++++------ src/sysc/kernel/sc_simcontext.h | 50 +++++++++++------------ src/sysc/log/sc_log.cpp | 46 ++++++++------------- src/sysc/log/sc_log.h | 36 +++++++++-------- src/sysc/log/sc_log_types.h | 67 +++++++++++++------------------ tests/systemc/sc_log/test01.cpp | 47 +++++++++++++--------- 6 files changed, 127 insertions(+), 145 deletions(-) diff --git a/src/sysc/kernel/sc_simcontext.cpp b/src/sysc/kernel/sc_simcontext.cpp index 8b0214912..03dcaebe0 100644 --- a/src/sysc/kernel/sc_simcontext.cpp +++ b/src/sysc/kernel/sc_simcontext.cpp @@ -2076,7 +2076,7 @@ SC_API void sc_unregister_stage_callback(sc_stage_callback_if & cb, // Implementation defined Dynamic log verbosity dispatch // void sc_simcontext::set_log_verbosity_fn( - std::function fn) + std::function fn) { if (dynamic_log_verbosity) { SC_REPORT_WARNING(SC_LOG_OVERWRITE_VERBOSITY_FN_, 0); @@ -2085,28 +2085,28 @@ void sc_simcontext::set_log_verbosity_fn( } } -log_levels sc_simcontext::get_log_verbosity(sc_log_logger_cache &logger, - const char *sc_name, - const char *typ_name) -{ +sc_log_level sc_simcontext::get_log_verbosity(sc_log_logger_cache &logger, + const char *file, + int line, + std::string_view sc_name, + const char *typ_name) { if (dynamic_log_verbosity) - return dynamic_log_verbosity(logger, sc_name, typ_name); + return dynamic_log_verbosity(logger, file, line, sc_name, typ_name); else return as_log(sc_report_handler::get_verbosity_level()); } // NB these functions are NOT a standard API, but are // exposed to provide access to the (implementation defined) dynamic logging configuration -void sc_set_log_verbosity_fn( - std::function fn) +void sc_log_impl::sc_set_log_verbosity_fn( + std::function fn) { sc_get_curr_simcontext()->set_log_verbosity_fn(std::move(fn)); } -log_levels sc_get_log_verbosity(sc_log_logger_cache &logger, - const char *sc_name, - const char *typ_name) -{ - return sc_get_curr_simcontext()->get_log_verbosity(logger, sc_name, typ_name); +sc_log_level sc_log_impl::sc_get_log_verbosity(sc_log_logger_cache &logger, const char *file, + int line, std::string_view sc_name, + const char *typ_name) { + return sc_get_curr_simcontext()->get_log_verbosity(logger, file, line, sc_name, typ_name); } } // namespace sc_core diff --git a/src/sysc/kernel/sc_simcontext.h b/src/sysc/kernel/sc_simcontext.h index 4e2d95772..c4d0d1132 100644 --- a/src/sysc/kernel/sc_simcontext.h +++ b/src/sysc/kernel/sc_simcontext.h @@ -79,7 +79,7 @@ class sc_cthread_process; class sc_thread_process; class sc_reset_finder; class sc_stub_registry; -class sc_log_global_logger_handler; +//class sc_log_global_logger_handler; extern sc_simcontext* sc_get_curr_simcontext(); @@ -150,24 +150,27 @@ SC_API void sc_register_stage_callback(sc_stage_callback_if & cb, SC_API void sc_unregister_stage_callback(sc_stage_callback_if & cb, unsigned int mask); +struct sc_log_impl { // +------------------------------------------------------------------------------------------------ // | Non standard API: Install a callback used to determine the effective log level for a given // | (sc_name, typ_name) pair. The cache parameter may be used to remember // | a computed level, once set the function will not be re-called. // +------------------------------------------------------------------------------------------------ -void sc_set_log_verbosity_fn( - std::function fn); +static void sc_set_log_verbosity_fn(std::function + fn); // +------------------------------------------------------------------------------------------------ // | Non standard API: Query the current log verbosity for the given handle cache and identifiers. -// | If no callback has been installed, the implementation shall fall back to +// | If no callback has been installed, the implementation falls back to // | the global report verbosity. // +------------------------------------------------------------------------------------------------ -sc_core::log_levels sc_get_log_verbosity( - sc_core::sc_log_logger_cache &logger, - const char *sc_name, - const char *typ_name); +static sc_core::sc_log_level sc_get_log_verbosity(sc_core::sc_log_logger_cache &logger, + const char *file, int line, + std::string_view sc_name, + const char *typ_name); +}; class sc_invoke_method; @@ -203,7 +206,6 @@ class SC_API sc_simcontext friend class sc_prim_channel; friend class sc_cthread_process; friend class sc_thread_process; - friend class sc_core::sc_log_global_logger_handler; friend struct sc_core::sc_log_logger_cache; friend SC_API sc_dt::uint64 sc_delta_count(); friend SC_API const std::vector& sc_get_top_level_events( @@ -229,13 +231,7 @@ class SC_API sc_simcontext friend SC_API void sc_unregister_stage_callback(sc_stage_callback_if & cb, unsigned int mask); - friend void sc_set_log_verbosity_fn( - std::function fn); - - friend sc_core::log_levels - sc_get_log_verbosity(sc_core::sc_log_logger_cache &logger, - const char *sc_name, const char *typ_name); + friend struct sc_log_impl; enum sc_signal_write_check { @@ -361,13 +357,17 @@ class SC_API sc_simcontext void pre_suspend() const; void post_suspend() const; - void set_log_verbosity_fn( - std::function fn); - sc_core::log_levels get_log_verbosity( - sc_core::sc_log_logger_cache &logger, - const char *sc_name, - const char *typ_name); +private: + void set_log_verbosity_fn(std::function + fn); + sc_core::sc_log_level get_log_verbosity( + sc_core::sc_log_logger_cache &, + const char *file, + int line, + std::string_view sc_name, + const char *typ_name=""); private: void hierarchy_push(sc_object_host*); @@ -418,7 +418,7 @@ class SC_API sc_simcontext inline void set_simulation_status(sc_status status); - std::function dynamic_log_verbosity; + std::function dynamic_log_verbosity; private: diff --git a/src/sysc/log/sc_log.cpp b/src/sysc/log/sc_log.cpp index 3b65f3956..f263ed739 100644 --- a/src/sysc/log/sc_log.cpp +++ b/src/sysc/log/sc_log.cpp @@ -31,61 +31,47 @@ namespace { // races in the unordered_map #ifdef DISABLE_REPORT_THREAD_LOCAL -std::unordered_map lut; +std::unordered_map lut; #else -thread_local std::unordered_map lut; +thread_local std::unordered_map lut; #endif // BKDR hash algorithm -auto char_hash(char const *str) -> uint64_t { +auto char_hash(std::string_view str) -> uint64_t { constexpr unsigned int seed = 131; // 31 131 1313 13131131313 etc// uint64_t hash = 0; - while (*str) { - hash = (hash * seed) + (*str); - str++; + for (char c: str) { + hash = (hash * seed) + static_cast(c); } return hash; } } // namespace -sc_core::log_levels -sc_core::sc_log_logger_cache::get_log_verbosity_cached(const char *scname, +sc_core::sc_log_level +sc_core::sc_log_logger_cache::get_log_verbosity_cached(const char *file, int line, std::string_view scname, const char *tname = "") { - if (level != sc_core::log_levels::UNSET) { + if (level != sc_core::sc_log_level::UNSET) { return level; } - if (!scname && features.size()) - scname = features[0].c_str(); - if (!scname) + if (!scname.data() && features.size()) + scname = features[0]; + if (!scname.data()) scname = ""; - type = std::string(scname); + type = scname; - return sc_core::sc_get_log_verbosity(*this, scname, tname); + return sc_core::sc_log_impl::sc_get_log_verbosity(*this, file, line, scname, tname); } -auto sc_core::get_log_verbosity(char const *str) -> sc_core::log_levels { - auto k = char_hash(str); +sc_core::sc_log_level sc_core::get_log_verbosity_uncached(const char *file, int line, std::string_view scname) { + auto k = char_hash(scname); auto it = lut.find(k); if (it != lut.end()) { return it->second; } sc_core::sc_log_logger_cache tmp; - lut[k] = tmp.get_log_verbosity_cached(str); + lut[k] = tmp.get_log_verbosity_cached(file, line, scname); return lut[k]; } - -sc_core::sc_log_global_logger_handler::sc_log_global_logger_handler() { - std::function - fn = [&](sc_core::sc_log_logger_cache &logger, const char *sc_name, - const char *t_name) -> sc_core::log_levels { - return operator()(logger, sc_name, t_name); - }; - ::sc_core::sc_set_log_verbosity_fn(fn); - ::sc_core::sc_report_handler::set_verbosity_level( - sc_core::SC_DEBUG); // Set the level in the core to DEBUG such that the - // handler can manage all levels of verbosity -} diff --git a/src/sysc/log/sc_log.h b/src/sysc/log/sc_log.h index 68fc459af..172bf6b50 100644 --- a/src/sysc/log/sc_log.h +++ b/src/sysc/log/sc_log.h @@ -75,7 +75,7 @@ static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; SC_LOG_HANDLE_NAME( \ SC_LOG_PRIV__EXPAND(SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))), \ SC_LOG_HANDLE_NAME()) = { \ - sc_core::log_levels::UNSET, \ + sc_core::sc_log_level::UNSET, \ "", \ {SC_LOG_PRIV__IIF(SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__)))( \ SC_LOG_PRIV__POP_ARG(__VA_ARGS__), ##__VA_ARGS__)}} @@ -84,7 +84,7 @@ static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; std::vector SC_LOG_HANDLE_NAME(NAME) #define SC_LOG_HANDLE_VECTOR_PUSH_BACK(NAME, ...) \ SC_LOG_HANDLE_NAME(NAME).push_back( \ - {sc_core::log_levels::UNSET, "", {__VA_ARGS__}}); + {sc_core::sc_log_level::UNSET, "", {__VA_ARGS__}}); // critical thing is that the initial if 'fails' as soon as possible - if it is // going to pass, we have all the time we want, as we will be logging anyway @@ -92,13 +92,16 @@ static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; // or a cache'd level /*** Helper macros for SC_LOG_ report macros ****/ -#define SC_LOG_VBSTY_CHECK_CACHED(lvl, features, cached, ...) \ - (cached.level >= lvl) && \ - (cached.get_log_verbosity_cached(sc_core::sc_log_priv__call_sc_name_fn()(this), \ - typeid(*this).name()) >= lvl) +#define MUST_BE_NON_STATIC_MEMBER_USE_STRING_TAG_INSTEAD static_cast(this) +#define SC_LOG_VBSTY_CHECK_CACHED(lvl, features, cached, ...) \ + (MUST_BE_NON_STATIC_MEMBER_USE_STRING_TAG_INSTEAD, \ + (cached.level >= lvl) && \ + (cached.get_log_verbosity_cached( \ + __FILE__, __LINE__, sc_core::sc_log_priv__call_sc_name_fn()(this), \ + typeid(*this).name()) >= lvl)) #define SC_LOG_VBSTY_CHECK_UNCACHED(lvl, ...) \ - (::sc_core::get_log_verbosity(__VA_ARGS__) >= lvl) + (::sc_core::get_log_verbosity_uncached(__FILE__, __LINE__, ##__VA_ARGS__) >= lvl) #define SC_LOG_VBSTY_CHECK(lvl, ...) \ SC_LOG_PRIV__IIF(SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))) \ @@ -114,8 +117,7 @@ static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; __VA_ARGS__, \ SC_LOG_HANDLE_NAME( \ SC_LOG_PRIV__EXPAND(SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))) \ - .type))), \ - __VA_ARGS__) + .type))), ##__VA_ARGS__) #define SC_LOG_PRIV__FMT_EMPTY_STR(...) std::format(__VA_ARGS__) @@ -127,13 +129,13 @@ static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; /*** End HELPER Macros *******/ #define SC_LOG_AT(lvl, ...) \ - if (SC_LOG_VBSTY_CHECK(lvl, __VA_ARGS__)) \ - SC_LOG_MSG(lvl, __VA_ARGS__) - -#define SC_CRITICAL(...) SC_LOG_AT(sc_core::log_levels::CRITICAL, __VA_ARGS__) -#define SC_WARN(...) SC_LOG_AT(sc_core::log_levels::WARN, __VA_ARGS__) -#define SC_INFO(...) SC_LOG_AT(sc_core::log_levels::INFO, __VA_ARGS__) -#define SC_DEBUG(...) SC_LOG_AT(sc_core::log_levels::DEBUG, __VA_ARGS__) -#define SC_TRACE(...) SC_LOG_AT(sc_core::log_levels::TRACE, __VA_ARGS__) + if (SC_LOG_VBSTY_CHECK(lvl, ##__VA_ARGS__)) \ + SC_LOG_MSG(lvl, ##__VA_ARGS__) + +#define SC_CRITICAL(...) SC_LOG_AT(sc_core::sc_log_level::CRITICAL, ##__VA_ARGS__) +#define SC_WARN(...) SC_LOG_AT(sc_core::sc_log_level::WARN, ##__VA_ARGS__) +#define SC_INFO(...) SC_LOG_AT(sc_core::sc_log_level::INFO, ##__VA_ARGS__) +#define SC_DEBUG(...) SC_LOG_AT(sc_core::sc_log_level::DEBUG, ##__VA_ARGS__) +#define SC_TRACE(...) SC_LOG_AT(sc_core::sc_log_level::TRACE, ##__VA_ARGS__) #endif /* _SC_LOG_H_ */ diff --git a/src/sysc/log/sc_log_types.h b/src/sysc/log/sc_log_types.h index 73484c1ed..0ea69fdaa 100644 --- a/src/sysc/log/sc_log_types.h +++ b/src/sysc/log/sc_log_types.h @@ -45,7 +45,7 @@ namespace sc_core { *based on SystemC "verbosity's" ************************/ -enum class log_levels { +enum class sc_log_level { NONE = sc_core::SC_NONE, CRITICAL = sc_core::SC_NONE, WARN = sc_core::SC_LOW, @@ -56,10 +56,10 @@ enum class log_levels { UNSET = INT_MAX }; -const static std::map log_level_map = { - {log_levels::CRITICAL, "CRITICAL"}, {log_levels::NONE, "NONE"}, - {log_levels::WARN, "WARN"}, {log_levels::INFO, "INFO"}, - {log_levels::DEBUG, "DEBUG"}, {log_levels::TRACE, "TRACE"}}; +const static std::map log_level_map = { + {sc_log_level::CRITICAL, "CRITICAL"}, {sc_log_level::NONE, "NONE"}, + {sc_log_level::WARN, "WARN"}, {sc_log_level::INFO, "INFO"}, + {sc_log_level::DEBUG, "DEBUG"}, {sc_log_level::TRACE, "TRACE"}}; /** * @fn log as_log(int) @@ -68,14 +68,14 @@ const static std::map log_level_map = { * @param logLevel the logging level * @return the log level */ -inline log_levels as_log(int logLevel) { +inline sc_log_level as_log(int logLevel) { auto m = log_level_map; for (auto l : m) { if (logLevel <= static_cast(l.first)) { return l.first; } } - return log_levels::TRACE; + return sc_log_level::TRACE; } /** @@ -85,13 +85,13 @@ inline log_levels as_log(int logLevel) { * @param logName the string name for the log level * @return the log level */ -inline log_levels as_log(std::string logName) { +inline sc_log_level as_log(std::string logName) { auto m = log_level_map; for (auto l : m) { if (logName == l.second) return l.first; } - return log_levels::TRACE; + return sc_log_level::TRACE; } /** * @fn std::istream& operator >>(std::istream&, log&) @@ -101,7 +101,7 @@ inline log_levels as_log(std::string logName) { * @param val the value holding the resulting value * @return the input stream */ -inline std::istream &operator>>(std::istream &is, log_levels &val) { +inline std::istream &operator>>(std::istream &is, sc_log_level &val) { std::string buf; is >> buf; val = as_log(buf); @@ -115,7 +115,7 @@ inline std::istream &operator>>(std::istream &is, log_levels &val) { * @param val logging level * @return reference to the stream for chaining */ -inline std::ostream &operator<<(std::ostream &os, log_levels const &val) { +inline std::ostream &operator<<(std::ostream &os, sc_log_level const &val) { auto m = log_level_map; os << m[val]; return os; @@ -154,7 +154,7 @@ class sc_log_priv__call_sc_name_fn { * */ struct sc_log_logger_cache { - log_levels level = log_levels::UNSET; + sc_log_level level = sc_log_level::UNSET; std::string type = ""; std::vector features; @@ -163,49 +163,36 @@ struct sc_log_logger_cache { * * @return log */ - log_levels get_log_verbosity_cached(const char *, const char *); + sc_log_level get_log_verbosity_cached(const char *, int, std::string_view, const char *); }; -class sc_log_global_logger_handler { -public: - virtual log_levels operator()(struct sc_log_logger_cache &logger, - std::string_view scname, - const char *tname) const = 0; - sc_log_global_logger_handler(); -}; - -inline log_levels get_log_verbosity() { - return static_cast( - ::sc_core::sc_report_handler::get_verbosity_level()); -} /** - * @fn sc_core::sc_verbosity get_log_verbosity(const char*) + * @fn sc_core::sc_verbosity get_log_verbosity_uncached(const char*) * @brief get the scope-based verbosity level * * The function returns a scope specific verbosity level if defined (e.g. by * using a CCI param named "log_level"). Otherwise the global verbosity level - * is being returned + * is being returned. Note the type name is not available as this form is + * expected to be used in static functions. * - * @param t the SystemC hierarchy scope name + * @param t the tag name being used (potentially the hierarchy name) * @return the verbosity level */ -log_levels get_log_verbosity(char const *t); +sc_log_level get_log_verbosity_uncached(char const *file, int line, std::string_view scname); + /** - * @fn sc_core::sc_verbosity get_log_verbosity(const char*) - * @brief get the scope-based verbosity level + * @fn sc_core::sc_verbosity get_log_verbosity_uncached() + * @brief get the global verbosity level * - * The function returns a scope specific verbosity level if defined (e.g. by - * using a CCI param named "log_level"). Otherwise the global verbosity level - * is being returned + * This is a special case when the user does not provide any tag * - * @param t the SystemC hierarchy scope name * @return the verbosity level */ -inline log_levels get_log_verbosity(std::string const &t) { - return get_log_verbosity(t.c_str()); +inline sc_log_level get_log_verbosity_uncached(char const *file, int line) { + return static_cast( + ::sc_core::sc_report_handler::get_verbosity_level()); } - /** * @brief Return list of logging parameters that have been used * @@ -232,7 +219,7 @@ struct sc_logger { * @param verbosity the log level */ sc_logger(const char *file, int line, - log_levels verbosity = sc_core::log_levels::INFO) + sc_log_level verbosity = sc_core::sc_log_level::INFO) : t(nullptr), file(file), line(line), level(verbosity) {} sc_logger() = delete; @@ -309,7 +296,7 @@ struct sc_logger { char *t{nullptr}; const char *file; const int line; - const log_levels level; + const sc_log_level level; }; } // namespace sc_core diff --git a/tests/systemc/sc_log/test01.cpp b/tests/systemc/sc_log/test01.cpp index c85fcc54e..a9ba3b8d3 100644 --- a/tests/systemc/sc_log/test01.cpp +++ b/tests/systemc/sc_log/test01.cpp @@ -76,10 +76,12 @@ SC_MODULE(mod_a) { * * void sc_set_log_verbosity_fn( * std::function< - * sc_core::log_levels( - * sc_core::sc_log_logger_cache&, - * const char*, - * const char* + * sc_core::sc_log_level( + * sc_core::sc_log_logger_cache &logger, + * const char *file, + * int line, + * std::string_view sc_name, + * const char *typ_name * ) * > fn * ); @@ -91,12 +93,14 @@ SC_MODULE(mod_a) { * identifiers. * * If no callback has been installed, the implementation - * shall fall back to the global report verbosity. + * will fall back to the global report verbosity. * -------------------------------------------------------- * - * sc_core::log_levels sc_get_log_verbosity( + * sc_core::sc_log_level sc_log_impl::sc_get_log_verbosity( * sc_core::sc_log_logger_cache &logger, - * const char *sc_name, + * const char *file, + * int line, + * std::string_view sc_name, * const char *typ_name); * ); * @@ -119,30 +123,33 @@ SC_MODULE(mod_a) { class scp_logger_test { std::unordered_set loggers; - sc_core::log_levels operator()(struct sc_core::sc_log_logger_cache &logger, + sc_core::sc_log_level operator()(struct sc_core::sc_log_logger_cache &logger, + const char *file, + int line, std::string_view scname, - const char *tname) { + const char *tname + ) { loggers.insert(&logger); if (logger.features.size() && logger.features[0] == "test_handler") { - return sc_core::log_levels::INFO; + return sc_core::sc_log_level::INFO; } if (scname == "sc_log_test") { /* test every time, and dont cache */ - return sc_core::log_levels::WARN; + return sc_core::sc_log_level::WARN; } /* Cache this one which will catch the normal SCMOD case for mod_a */ - logger.level = sc_core::log_levels::TRACE; - return sc_core::log_levels::TRACE; + logger.level = sc_core::sc_log_level::TRACE; + return sc_core::sc_log_level::TRACE; } public: scp_logger_test() { - std::function - fn = [&](sc_core::sc_log_logger_cache &logger, const char *sc_name, - const char *t_name) -> sc_core::log_levels { - return operator()(logger, sc_name, t_name); + std::function + fn = [&](sc_core::sc_log_logger_cache &logger, const char *file, int line, std::string_view sc_name, + const char *t_name) -> sc_core::sc_log_level { + return operator()(logger, file, line, sc_name, t_name); }; - ::sc_core::sc_get_curr_simcontext()->set_log_verbosity_fn(fn); + ::sc_core::sc_log_impl::sc_set_log_verbosity_fn(fn); ::sc_core::sc_report_handler::set_verbosity_level( sc_core::SC_DEBUG); // Set the level in the core to DEBUG such that the // handler can manage all levels of verbosity @@ -150,7 +157,7 @@ class scp_logger_test { void reset() { for (auto *logger : loggers) { if (logger) { - logger->level = sc_core::log_levels::UNSET; + logger->level = sc_core::sc_log_level::UNSET; } } } From d825db5ca7e96c0f1a3db3f686bd81d83f301c89 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 12 Feb 2026 17:27:44 +0100 Subject: [PATCH 15/50] Major re-work to simplify macro API Signed-off-by: Mark Burton --- src/sysc/kernel/sc_module.h | 3 +- src/sysc/kernel/sc_simcontext.cpp | 14 +- src/sysc/kernel/sc_simcontext.h | 17 +- src/sysc/log/sc_log.cpp | 80 ++--- src/sysc/log/sc_log.h | 253 +++++++++----- src/sysc/log/sc_log_types.h | 108 +++--- tests/systemc/sc_log/golden/test01.log | 35 -- tests/systemc/sc_log/test01/golden/test01.log | 48 +++ tests/systemc/sc_log/{ => test01}/test01.cpp | 119 +++++-- tests/systemc/sc_log/test02/golden/test02.log | 81 +++++ tests/systemc/sc_log/test02/test02.cpp | 310 ++++++++++++++++++ 11 files changed, 808 insertions(+), 260 deletions(-) delete mode 100644 tests/systemc/sc_log/golden/test01.log create mode 100644 tests/systemc/sc_log/test01/golden/test01.log rename tests/systemc/sc_log/{ => test01}/test01.cpp (62%) create mode 100644 tests/systemc/sc_log/test02/golden/test02.log create mode 100644 tests/systemc/sc_log/test02/test02.cpp diff --git a/src/sysc/kernel/sc_module.h b/src/sysc/kernel/sc_module.h index 8ed7416ef..bcfca4729 100644 --- a/src/sysc/kernel/sc_module.h +++ b/src/sysc/kernel/sc_module.h @@ -41,6 +41,7 @@ #include "sysc/kernel/sc_wait.h" #include "sysc/kernel/sc_wait_cthread.h" #include "sysc/utils/sc_list.h" +#include "sysc/log/sc_log.h" #include // std::remove_reference @@ -89,7 +90,7 @@ class SC_API sc_module friend class sc_simcontext; friend class sc_initializer_function; protected: - sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE; + SC_LOG_HANDLE(); public: sc_simcontext* sc_get_curr_simcontext() diff --git a/src/sysc/kernel/sc_simcontext.cpp b/src/sysc/kernel/sc_simcontext.cpp index 03dcaebe0..0dc24d074 100644 --- a/src/sysc/kernel/sc_simcontext.cpp +++ b/src/sysc/kernel/sc_simcontext.cpp @@ -2076,7 +2076,7 @@ SC_API void sc_unregister_stage_callback(sc_stage_callback_if & cb, // Implementation defined Dynamic log verbosity dispatch // void sc_simcontext::set_log_verbosity_fn( - std::function fn) + std::function fn) { if (dynamic_log_verbosity) { SC_REPORT_WARNING(SC_LOG_OVERWRITE_VERBOSITY_FN_, 0); @@ -2088,10 +2088,9 @@ void sc_simcontext::set_log_verbosity_fn( sc_log_level sc_simcontext::get_log_verbosity(sc_log_logger_cache &logger, const char *file, int line, - std::string_view sc_name, - const char *typ_name) { + std::string_view local_tag) { if (dynamic_log_verbosity) - return dynamic_log_verbosity(logger, file, line, sc_name, typ_name); + return dynamic_log_verbosity(logger, file, line, local_tag); else return as_log(sc_report_handler::get_verbosity_level()); } @@ -2099,14 +2098,13 @@ sc_log_level sc_simcontext::get_log_verbosity(sc_log_logger_cache &logger, // NB these functions are NOT a standard API, but are // exposed to provide access to the (implementation defined) dynamic logging configuration void sc_log_impl::sc_set_log_verbosity_fn( - std::function fn) + std::function fn) { sc_get_curr_simcontext()->set_log_verbosity_fn(std::move(fn)); } sc_log_level sc_log_impl::sc_get_log_verbosity(sc_log_logger_cache &logger, const char *file, - int line, std::string_view sc_name, - const char *typ_name) { - return sc_get_curr_simcontext()->get_log_verbosity(logger, file, line, sc_name, typ_name); + int line, std::string_view local_tag) { + return sc_get_curr_simcontext()->get_log_verbosity(logger, file, line, local_tag); } } // namespace sc_core diff --git a/src/sysc/kernel/sc_simcontext.h b/src/sysc/kernel/sc_simcontext.h index c4d0d1132..52203fa4e 100644 --- a/src/sysc/kernel/sc_simcontext.h +++ b/src/sysc/kernel/sc_simcontext.h @@ -79,7 +79,6 @@ class sc_cthread_process; class sc_thread_process; class sc_reset_finder; class sc_stub_registry; -//class sc_log_global_logger_handler; extern sc_simcontext* sc_get_curr_simcontext(); @@ -153,23 +152,22 @@ SC_API void sc_unregister_stage_callback(sc_stage_callback_if & cb, struct sc_log_impl { // +------------------------------------------------------------------------------------------------ // | Non standard API: Install a callback used to determine the effective log level for a given -// | (sc_name, typ_name) pair. The cache parameter may be used to remember +// | logger cache and local_tag. The cache parameter may be used to remember // | a computed level, once set the function will not be re-called. // +------------------------------------------------------------------------------------------------ static void sc_set_log_verbosity_fn(std::function + int, std::string_view)> fn); // +------------------------------------------------------------------------------------------------ -// | Non standard API: Query the current log verbosity for the given handle cache and identifiers. +// | Non standard API: Query the current log verbosity for the given handle cache and local_tag. // | If no callback has been installed, the implementation falls back to // | the global report verbosity. // +------------------------------------------------------------------------------------------------ static sc_core::sc_log_level sc_get_log_verbosity(sc_core::sc_log_logger_cache &logger, const char *file, int line, - std::string_view sc_name, - const char *typ_name); + std::string_view local_tag = {}); }; class sc_invoke_method; @@ -360,14 +358,13 @@ class SC_API sc_simcontext private: void set_log_verbosity_fn(std::function + int, std::string_view)> fn); sc_core::sc_log_level get_log_verbosity( sc_core::sc_log_logger_cache &, const char *file, int line, - std::string_view sc_name, - const char *typ_name=""); + std::string_view local_tag = {}); private: void hierarchy_push(sc_object_host*); @@ -418,7 +415,7 @@ class SC_API sc_simcontext inline void set_simulation_status(sc_status status); - std::function dynamic_log_verbosity; + std::function dynamic_log_verbosity; private: diff --git a/src/sysc/log/sc_log.cpp b/src/sysc/log/sc_log.cpp index f263ed739..c194b5e2d 100644 --- a/src/sysc/log/sc_log.cpp +++ b/src/sysc/log/sc_log.cpp @@ -16,62 +16,46 @@ sc_log.h --SystemC logging functions. Original Author: Eyck Jentzsch, MINRES Technologies GmbH Mark Burton, Qualcomm Technologies, Inc. - + CHANGE LOG AT THE END OF THE FILE *****************************************************************************/ - -#include "sysc/log/sc_log_types.h" #include "sysc/kernel/sc_simcontext.h" +#include "sysc/log/sc_log_types.h" +#include +#include #include -namespace { -// Making this thread_local could cause thread copies of the same cache -// entries, but more likely naming will be thread local too, and this avoids -// races in the unordered_map - -#ifdef DISABLE_REPORT_THREAD_LOCAL -std::unordered_map lut; -#else -thread_local std::unordered_map lut; -#endif - -// BKDR hash algorithm -auto char_hash(std::string_view str) -> uint64_t { - constexpr unsigned int seed = 131; // 31 131 1313 13131131313 etc// - uint64_t hash = 0; - for (char c: str) { - hash = (hash * seed) + static_cast(c); - } - return hash; -} -} // namespace - -sc_core::sc_log_level -sc_core::sc_log_logger_cache::get_log_verbosity_cached(const char *file, int line, std::string_view scname, - const char *tname = "") { - if (level != sc_core::sc_log_level::UNSET) { +namespace sc_core { + +// Definition of log level map (declared as extern in sc_log_types.h) +// This avoids creating duplicate copies in each translation unit. +const std::map log_level_map = { + {sc_log_level::CRITICAL, "CRITICAL"}, + {sc_log_level::NONE, "NONE"}, + {sc_log_level::WARN, "WARN"}, + {sc_log_level::INFO, "INFO"}, + {sc_log_level::DEBUG, "DEBUG"}, + {sc_log_level::TRACE, "TRACE"} +}; + +sc_log_level sc_log_logger_cache::get_log_verbosity_cached( + const char *file, int line, std::string_view local_tag) { + if (level != sc_log_level::UNSET) { return level; } - if (!scname.data() && features.size()) - scname = features[0]; - if (!scname.data()) - scname = ""; - - type = scname; - - return sc_core::sc_log_impl::sc_get_log_verbosity(*this, file, line, scname, tname); + return sc_log_impl::sc_get_log_verbosity(*this, file, line, local_tag); } -sc_core::sc_log_level sc_core::get_log_verbosity_uncached(const char *file, int line, std::string_view scname) { - auto k = char_hash(scname); - auto it = lut.find(k); - if (it != lut.end()) { - return it->second; - } - - sc_core::sc_log_logger_cache tmp; - lut[k] = tmp.get_log_verbosity_cached(file, line, scname); - return lut[k]; -} +} // namespace sc_core + +// Global default logger with empty tag (in global namespace for proper name +// shadowing). This logger is used when no specific logger handle is provided. +// Note: Using string_view{} for empty views and nullptr for typename_str. +sc_core::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE{ + sc_core::sc_log_level::UNSET, // level + std::string_view{}, // tag (empty) + std::string_view{}, // scname (empty) + nullptr // typename_str (no type info) +}; diff --git a/src/sysc/log/sc_log.h b/src/sysc/log/sc_log.h index 172bf6b50..d05cd4ede 100644 --- a/src/sysc/log/sc_log.h +++ b/src/sysc/log/sc_log.h @@ -16,7 +16,7 @@ sc_log.h --SystemC logging functions. Original Author: Eyck Jentzsch, MINRES Technologies GmbH Mark Burton, Qualcomm Technologies, Inc. - + CHANGE LOG AT THE END OF THE FILE *****************************************************************************/ @@ -27,115 +27,194 @@ #include #include +#include #include #include #include +#include #include -#include #include -// must be global for macro to work. +// Declaration of global default logger (defined in sc_log.cpp, in global +// namespace) +extern sc_core::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE; + +/****************************************************************************** + * PRIVATE IMPLEMENTATION HELPER MACROS + * + * These macros are internal implementation details and should NOT be used + * directly by user code. They are prefixed with SC_LOG_PRIV__ to indicate + * they are private implementation details. + ******************************************************************************/ + +// 2-step token pasting (forces expansion before concatenation) +#define SC_LOG_PRIV__CAT_IMPL(a, b) a##b +#define SC_LOG_PRIV__CAT(a, b) SC_LOG_PRIV__CAT_EVAL(a, b) +#define SC_LOG_PRIV__CAT_EVAL(a, b) SC_LOG_PRIV__CAT_IMPL(a, b) + +// Argument counting: supports 0, 1, or 2 arguments +#define SC_LOG_PRIV__NARG(...) SC_LOG_PRIV__NARG_IMPL(0, ##__VA_ARGS__, 2, 1, 0) +#define SC_LOG_PRIV__NARG_IMPL(_0, _1, _2, N, ...) N + +// Dispatch to appropriate macro based on argument count +#define SC_LOG_PRIV__DISPATCH(func, ...) \ + SC_LOG_PRIV__DISPATCH_IMPL(func, SC_LOG_PRIV__NARG(__VA_ARGS__), __VA_ARGS__) +#define SC_LOG_PRIV__DISPATCH_IMPL(func, count, ...) \ + SC_LOG_PRIV__CAT(func, count)(__VA_ARGS__) + +// Detect whether a macro argument is a "logger handle" or a "tag" +#define SC_LOG_PRIV__IS_LOGGER_HANDLE(x) \ + std::is_same_v, sc_core::sc_log_logger_cache> + +// Note: SC_LOG_PRIV__FMT_EMPTY_STR is both a const char* and a function-like +// macro When used without parentheses, it's the empty string; with parentheses, +// it calls std::format static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; +#define SC_LOG_PRIV__FMT_EMPTY_STR(...) std::format(__VA_ARGS__) + +// Internal verbosity check variants (used by public SC_LOG_AT macro) +#define SC_LOG_PRIV__VBSTY_CHECK0(lvl) \ + ((SC_LOG_LOG_LEVEL_CACHE.level >= (lvl)) && \ + (SC_LOG_LOG_LEVEL_CACHE.get_log_verbosity_cached(__FILE__, __LINE__) >= \ + (lvl))) + +#define SC_LOG_PRIV__VBSTY_CHECK1(lvl, arg1) \ + ([&](auto &&x) -> bool { \ + if constexpr (SC_LOG_PRIV__IS_LOGGER_HANDLE(x)) { \ + return ((x.level >= (lvl)) && \ + (x.get_log_verbosity_cached(__FILE__, __LINE__) >= (lvl))); \ + } else { \ + return SC_LOG_PRIV__VBSTY_CHECK2(lvl, SC_LOG_LOG_LEVEL_CACHE, x); \ + } \ + }(arg1)) + +#define SC_LOG_PRIV__VBSTY_CHECK2(lvl, logger, tag) \ + ((logger.level >= lvl) && \ + (logger.get_log_verbosity_cached(__FILE__, __LINE__, tag) >= lvl)) + +#define SC_LOG_PRIV__VBSTY_CHECK_IMPL(count, lvl, ...) \ + SC_LOG_PRIV__CAT(SC_LOG_PRIV__VBSTY_CHECK, count)(lvl, ##__VA_ARGS__) + +// Internal tag extraction variants (used by public SC_LOG_MSG macro) +#define SC_LOG_PRIV__GET_TAG0() \ + (SC_LOG_LOG_LEVEL_CACHE.tag.empty() ? SC_LOG_LOG_LEVEL_CACHE.scname.data() \ + : SC_LOG_LOG_LEVEL_CACHE.tag.data()) + +#define SC_LOG_PRIV__GET_TAG1(arg1) \ + ([&](auto &&x) -> const char * { \ + if constexpr (SC_LOG_PRIV__IS_LOGGER_HANDLE(x)) { \ + return (x.tag.empty() ? x.scname.data() : x.tag.data()); \ + } else { \ + return x; \ + } \ + }(arg1)) + +#define SC_LOG_PRIV__GET_TAG2(logger, tag) tag + +// Internal handle variants (used by public SC_LOG_HANDLE macro) +#define SC_LOG_PRIV__HANDLE0() \ + sc_core::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE = \ + sc_core::sc_log_handle_factory::make(sc_core::sc_log_level::UNSET, "", \ + this) + +#define SC_LOG_PRIV__HANDLE1(tag_str) \ + sc_core::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE = \ + sc_core::sc_log_handle_factory::make(sc_core::sc_log_level::UNSET, \ + tag_str, this) + +#define SC_LOG_PRIV__HANDLE2(logger_name, tag_str) \ + sc_core::sc_log_logger_cache SC_LOG_PRIV__HANDLE_NAME(logger_name) = \ + sc_core::sc_log_handle_factory::make(sc_core::sc_log_level::UNSET, \ + tag_str, this) + +// Internal static handle variants (used by public SC_LOG_HANDLE_STATIC macro) +#define SC_LOG_PRIV__HANDLE_STATIC1(tag_str) \ + static sc_core::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE = \ + sc_core::sc_log_handle_factory::make_static( \ + sc_core::sc_log_level::UNSET, tag_str) + +#define SC_LOG_PRIV__HANDLE_STATIC2(logger_name, tag_str) \ + static sc_core::sc_log_logger_cache SC_LOG_PRIV__HANDLE_NAME(logger_name) = \ + sc_core::sc_log_handle_factory::make_static( \ + sc_core::sc_log_level::UNSET, tag_str) + +// Helper to get logger handle name +#define SC_LOG_PRIV__HANDLE_NAME(x) x + +/****************************************************************************** + * PUBLIC API MACROS + * + * These macros form the public logging API and should be used by user code. + ******************************************************************************/ /** - * logging macros + * SC_LOG_HANDLE - Declare a logger handle in a class + * + * Usage: + * SC_LOG_HANDLE() // Default logger with no tag + * SC_LOG_HANDLE("MyTag") // Default logger with tag + * SC_LOG_HANDLE(my_logger, "MyTag") // Named logger with tag */ +#define SC_LOG_HANDLE(...) \ + SC_LOG_PRIV__DISPATCH(SC_LOG_PRIV__HANDLE, ##__VA_ARGS__) /** - * Boilerplate convenience macros + * SC_LOG_HANDLE_STATIC - Declare a static/global logger handle + * + * Usage: + * SC_LOG_HANDLE_STATIC("MyTag") // static logger with tag + * SC_LOG_HANDLE_STATIC(my_logger, "MyTag") // Named static logger with tag + * + * Note: The 0 and 1 argument forms create a file-local static logger that + * shadows the global SC_LOG_LOG_LEVEL_CACHE within that translation unit. + * This is useful for .cpp files that want their own default logger. */ -#define SC_LOG_PRIV__CAT(a, ...) SC_LOG_PRIV__PRIMITIVE_CAT(a, __VA_ARGS__) -#define SC_LOG_PRIV__PRIMITIVE_CAT(a, ...) a##__VA_ARGS__ - -#define SC_LOG_PRIV__IIF(c) SC_LOG_PRIV__PRIMITIVE_CAT(SC_LOG_PRIV__IIF_, c) -#define SC_LOG_PRIV__IIF_0(t, ...) __VA_ARGS__ -#define SC_LOG_PRIV__IIF_1(t, ...) t - -#define SC_LOG_PRIV__CHECK_N(x, n, ...) n -#define SC_LOG_PRIV__CHECK(...) SC_LOG_PRIV__CHECK_N(__VA_ARGS__, 0, ) -#define SC_LOG_PRIV__PROBE(x) x, 1, - -#define SC_LOG_PRIV__EXPAND(...) __VA_ARGS__ - -#define SC_LOG_PRIV__FIRST_ARG(f, ...) f -#define SC_LOG_PRIV__POP_ARG(f, ...) __VA_ARGS__ - -#define SC_LOG_PRIV__IS_PAREN(x) SC_LOG_PRIV__CHECK(SC_LOG_PRIV__IS_PAREN_PROBE x) -#define SC_LOG_PRIV__IS_PAREN_PROBE(...) SC_LOG_PRIV__PROBE(~) -/********/ - -/* default logger cache name */ -#define SC_LOG_HANDLE_NAME(x) SC_LOG_PRIV__CAT(SC_LOG_LOG_LEVEL_CACHE, x) - -/* User interface macros */ -#define SC_LOG_HANDLE(...) \ - sc_core::sc_log_logger_cache SC_LOG_PRIV__IIF( \ - SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__)))( \ - SC_LOG_HANDLE_NAME( \ - SC_LOG_PRIV__EXPAND(SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))), \ - SC_LOG_HANDLE_NAME()) = { \ - sc_core::sc_log_level::UNSET, \ - "", \ - {SC_LOG_PRIV__IIF(SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__)))( \ - SC_LOG_PRIV__POP_ARG(__VA_ARGS__), ##__VA_ARGS__)}} +#define SC_LOG_HANDLE_STATIC(...) \ + SC_LOG_PRIV__DISPATCH(SC_LOG_PRIV__HANDLE_STATIC, ##__VA_ARGS__) +/** + * SC_LOG_HANDLE_VECTOR - Declare a vector of logger handles + */ #define SC_LOG_HANDLE_VECTOR(NAME) \ - std::vector SC_LOG_HANDLE_NAME(NAME) -#define SC_LOG_HANDLE_VECTOR_PUSH_BACK(NAME, ...) \ - SC_LOG_HANDLE_NAME(NAME).push_back( \ - {sc_core::sc_log_level::UNSET, "", {__VA_ARGS__}}); - -// critical thing is that the initial if 'fails' as soon as possible - if it is -// going to pass, we have all the time we want, as we will be logging anyway -// This HAS to be done as a macro, because the first argument may be a string -// or a cache'd level - -/*** Helper macros for SC_LOG_ report macros ****/ -#define MUST_BE_NON_STATIC_MEMBER_USE_STRING_TAG_INSTEAD static_cast(this) -#define SC_LOG_VBSTY_CHECK_CACHED(lvl, features, cached, ...) \ - (MUST_BE_NON_STATIC_MEMBER_USE_STRING_TAG_INSTEAD, \ - (cached.level >= lvl) && \ - (cached.get_log_verbosity_cached( \ - __FILE__, __LINE__, sc_core::sc_log_priv__call_sc_name_fn()(this), \ - typeid(*this).name()) >= lvl)) - -#define SC_LOG_VBSTY_CHECK_UNCACHED(lvl, ...) \ - (::sc_core::get_log_verbosity_uncached(__FILE__, __LINE__, ##__VA_ARGS__) >= lvl) - -#define SC_LOG_VBSTY_CHECK(lvl, ...) \ - SC_LOG_PRIV__IIF(SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))) \ - (SC_LOG_VBSTY_CHECK_CACHED( \ - lvl, SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__), \ - SC_LOG_HANDLE_NAME( \ - SC_LOG_PRIV__EXPAND(SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__)))), \ - SC_LOG_VBSTY_CHECK_UNCACHED(lvl, ##__VA_ARGS__)) - -#define SC_LOG_GET_FEATURES(...) \ - SC_LOG_PRIV__IIF(SC_LOG_PRIV__IS_PAREN(SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))) \ - (SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__EXPAND((SC_LOG_PRIV__POP_ARG( \ - __VA_ARGS__, \ - SC_LOG_HANDLE_NAME( \ - SC_LOG_PRIV__EXPAND(SC_LOG_PRIV__FIRST_ARG SC_LOG_PRIV__FIRST_ARG(__VA_ARGS__))) \ - .type))), ##__VA_ARGS__) + std::vector SC_LOG_PRIV__HANDLE_NAME(NAME) -#define SC_LOG_PRIV__FMT_EMPTY_STR(...) std::format(__VA_ARGS__) +/** + * SC_LOG_HANDLE_VECTOR_PUSH_BACK - Add a logger to a vector + */ +#define SC_LOG_HANDLE_VECTOR_PUSH_BACK(NAME, tag_str) \ + SC_LOG_PRIV__HANDLE_NAME(NAME).push_back( \ + {sc_core::sc_log_level::UNSET, "", tag_str}) -#define SC_LOG_MSG(lvl, ...) \ - ::sc_core::sc_logger<::sc_core::SC_INFO, false>(__FILE__, __LINE__, lvl) \ - .type(SC_LOG_GET_FEATURES(__VA_ARGS__)) \ +/** + * SC_LOG_AT - Log a message at a specific level + * + * Usage: + * SC_LOG_AT(sc_core::sc_log_level::INFO) << "message"; + * SC_LOG_AT(sc_core::sc_log_level::INFO, my_logger) << "message"; + * SC_LOG_AT(sc_core::sc_log_level::INFO, "tag") << "message"; + */ +#define SC_LOG_AT(lvl, ...) \ + if (SC_LOG_PRIV__VBSTY_CHECK_IMPL(SC_LOG_PRIV__NARG(__VA_ARGS__), lvl, \ + ##__VA_ARGS__)) \ + ::sc_core::sc_logger<::sc_core::SC_INFO, false>(__FILE__, __LINE__, lvl) \ + .type(SC_LOG_PRIV__DISPATCH(SC_LOG_PRIV__GET_TAG, ##__VA_ARGS__)) \ .get() \ << SC_LOG_PRIV__FMT_EMPTY_STR -/*** End HELPER Macros *******/ - -#define SC_LOG_AT(lvl, ...) \ - if (SC_LOG_VBSTY_CHECK(lvl, ##__VA_ARGS__)) \ - SC_LOG_MSG(lvl, ##__VA_ARGS__) -#define SC_CRITICAL(...) SC_LOG_AT(sc_core::sc_log_level::CRITICAL, ##__VA_ARGS__) +/** + * Convenience macros for common log levels + * + * Usage: + * SC_CRITICAL() << "message"; + * SC_WARN(my_logger) << "message"; + * SC_INFO("tag") << "message"; + */ +#define SC_CRITICAL(...) \ + SC_LOG_AT(sc_core::sc_log_level::CRITICAL, ##__VA_ARGS__) #define SC_WARN(...) SC_LOG_AT(sc_core::sc_log_level::WARN, ##__VA_ARGS__) #define SC_INFO(...) SC_LOG_AT(sc_core::sc_log_level::INFO, ##__VA_ARGS__) #define SC_DEBUG(...) SC_LOG_AT(sc_core::sc_log_level::DEBUG, ##__VA_ARGS__) #define SC_TRACE(...) SC_LOG_AT(sc_core::sc_log_level::TRACE, ##__VA_ARGS__) -#endif /* _SC_LOG_H_ */ +#endif /* _SC_LOG_H_ */ diff --git a/src/sysc/log/sc_log_types.h b/src/sysc/log/sc_log_types.h index 0ea69fdaa..d6984d971 100644 --- a/src/sysc/log/sc_log_types.h +++ b/src/sysc/log/sc_log_types.h @@ -13,10 +13,10 @@ permissions and limitations under the License. *****************************************************************************/ /***************************************************************************** - sc_log.h --SystemC logging functions. + sc_log_types.h -- SystemC logging type definitions and utilities. Original Author: Eyck Jentzsch, MINRES Technologies GmbH Mark Burton, Qualcomm Technologies, Inc. - + CHANGE LOG AT THE END OF THE FILE *****************************************************************************/ @@ -24,12 +24,13 @@ #define _SC_LOG_TYPES_H_ #include +#include #include #include #include #include +#include #include -#include #include @@ -56,10 +57,8 @@ enum class sc_log_level { UNSET = INT_MAX }; -const static std::map log_level_map = { - {sc_log_level::CRITICAL, "CRITICAL"}, {sc_log_level::NONE, "NONE"}, - {sc_log_level::WARN, "WARN"}, {sc_log_level::INFO, "INFO"}, - {sc_log_level::DEBUG, "DEBUG"}, {sc_log_level::TRACE, "TRACE"}}; +// Map of log levels to their string representations (defined in sc_log.cpp) +extern const std::map log_level_map; /** * @fn log as_log(int) @@ -121,11 +120,11 @@ inline std::ostream &operator<<(std::ostream &os, sc_log_level const &val) { return os; } -/* Convenience function to allow useage outside of SystemC hierarchy */ +/* Convenience helper to detect if a type has a name() method. + * This allows logging to work both inside and outside of SystemC hierarchy. */ class sc_log_priv__call_sc_name_fn { template - static auto test(T *p) - -> decltype(p->name(), std::true_type()); + static auto test(T *p) -> decltype(p->name(), std::true_type()); template static auto test(...) -> decltype(std::false_type()); template @@ -149,50 +148,79 @@ class sc_log_priv__call_sc_name_fn { /******************/ +// Forward declaration for sc_log_impl (defined in sc_simcontext.h) +struct sc_log_impl; + /** - * @brief cached logging information used in the (logger) form. + * @brief Cached logging information for a logger instance. * + * Type choices rationale: + * - tag: string_view - references string literals passed to SC_LOG_HANDLE macros + * - scname: string_view - references the result of this->name() which has + * static storage duration in sc_object + * - typename_str: const char* - directly stores the pointer returned by + * typeid().name() which has static storage duration + * + * These choices avoid unnecessary string copies while maintaining safety + * since all referenced strings have appropriate lifetimes. */ struct sc_log_logger_cache { sc_log_level level = sc_log_level::UNSET; - std::string type = ""; - std::vector features; + std::string_view tag{}; // Logger tag/identifier + std::string_view scname{}; // Captured sc_object name (from this->name()) + const char* typename_str = nullptr; // Captured type name (from typeid(*this).name()) /** * @brief Initialize the verbosity cache and/or return the cached value. * - * @return log + * This function checks if the level is already cached, and if not, + * queries the dynamic verbosity function. + * + * @param file source file name + * @param line source line number + * @param local_tag optional local tag that overrides the effective name + * @return log level for this logger */ - sc_log_level get_log_verbosity_cached(const char *, int, std::string_view, const char *); + sc_log_level get_log_verbosity_cached(const char *file, int line, + std::string_view local_tag = {}); }; /** - * @fn sc_core::sc_verbosity get_log_verbosity_uncached(const char*) - * @brief get the scope-based verbosity level + * @brief Factory for constructing sc_log_logger_cache objects with captured + * context. * - * The function returns a scope specific verbosity level if defined (e.g. by - * using a CCI param named "log_level"). Otherwise the global verbosity level - * is being returned. Note the type name is not available as this form is - * expected to be used in static functions. + * Note: C++ cannot infer an owning object's name/type from within the cache's + * default constructor, so the owning object pointer must be provided here (when + * available). * - * @param t the tag name being used (potentially the hierarchy name) - * @return the verbosity level + * The factory returns string_view for tag and scname (which reference string + * literals or existing strings), and const char* for typename_str (from typeid). */ +struct sc_log_handle_factory { + template + static sc_log_logger_cache make(sc_log_level lvl, const char *tag_str, + TYPE *p) { + const char *n = sc_log_priv__call_sc_name_fn{}(p); + const char *t = typeid(*p).name(); + return sc_log_logger_cache{ + lvl, + tag_str ? std::string_view(tag_str) : std::string_view(), + n ? std::string_view(n) : std::string_view(), + t // typeid().name() returns const char* with static storage + }; + } -sc_log_level get_log_verbosity_uncached(char const *file, int line, std::string_view scname); + static sc_log_logger_cache make_static(sc_log_level lvl, + const char *tag_str) { + return sc_log_logger_cache{ + lvl, + tag_str ? std::string_view(tag_str) : std::string_view(), + std::string_view(), + nullptr // No type information for static loggers + }; + } +}; -/** - * @fn sc_core::sc_verbosity get_log_verbosity_uncached() - * @brief get the global verbosity level - * - * This is a special case when the user does not provide any tag - * - * @return the verbosity level - */ -inline sc_log_level get_log_verbosity_uncached(char const *file, int line) { - return static_cast( - ::sc_core::sc_report_handler::get_verbosity_level()); -} /** * @brief Return list of logging parameters that have been used * @@ -219,7 +247,7 @@ struct sc_logger { * @param verbosity the log level */ sc_logger(const char *file, int line, - sc_log_level verbosity = sc_core::sc_log_level::INFO) + sc_log_level verbosity = sc_core::sc_log_level::INFO) : t(nullptr), file(file), line(line), level(verbosity) {} sc_logger() = delete; @@ -301,6 +329,12 @@ struct sc_logger { } // namespace sc_core +// This macro is intentionally in the global namespace to allow it to be +// used as a member variable name in user classes without namespace qualification. +// The macro expands to _m_sc_log_log_level_cache_ which uses a leading underscore +// to indicate it's an implementation detail. While identifiers with leading +// underscores are generally reserved, this pattern (_m_*) is safe as it doesn't +// conflict with reserved patterns (__* or _Capital*). #define SC_LOG_LOG_LEVEL_CACHE _m_sc_log_log_level_cache_ /** @} */ // end of sc_log diff --git a/tests/systemc/sc_log/golden/test01.log b/tests/systemc/sc_log/golden/test01.log deleted file mode 100644 index 134c7f64b..000000000 --- a/tests/systemc/sc_log/golden/test01.log +++ /dev/null @@ -1,35 +0,0 @@ -SystemC Simulation -0 is log_level CRITICAL -50 is log_level WARN -100 is log_level WARN -150 is log_level INFO -200 is log_level INFO -250 is log_level DEBUG -300 is log_level DEBUG -350 is log_level TRACE -400 is log_level TRACE -450 is log_level TRACE -Test string based handler -TEST REPORT: CRITICAL : [sc_log_test] CRITICAL -TEST REPORT: WARN : [sc_log_test] WARN -test FMT string -TEST REPORT: WARN : [SystemC] Testing FMT Hello world -construct module -TEST REPORT: CRITICAL : [MyMod] Log to name() (at level CRITICAL) -TEST REPORT: CRITICAL : [MyMod] Log to default () (at level CRITICAL) -TEST REPORT: CRITICAL : [MyMod] Log to test_handler (at level CRITICAL) -TEST REPORT: WARN : [MyMod] Log to name() (at level WARN) -TEST REPORT: WARN : [MyMod] Log to default () (at level WARN) -TEST REPORT: WARN : [MyMod] Log to test_handler (at level WARN) -TEST REPORT: INFO : [MyMod] Log to name() (at level INFO) -TEST REPORT: INFO : [MyMod] Log to default () (at level INFO) -TEST REPORT: INFO : [MyMod] Log to test_handler (at level INFO) -TEST REPORT: DEBUG : [MyMod] Log to name() (at level DEBUG) -TEST REPORT: DEBUG : [MyMod] Log to default () (at level DEBUG) -TEST REPORT: TRACE : [MyMod] Log to name() (at level TRACE) -TEST REPORT: TRACE : [MyMod] Log to default () (at level TRACE) -TEST REPORT: CRITICAL : [MyMod] SC_CRITICAL -TEST REPORT: WARN : [MyMod] SC_WARN -TEST REPORT: INFO : [MyMod] SC_INFO -TEST REPORT: DEBUG : [MyMod] SC_DEBUG -TEST REPORT: TRACE : [MyMod] SC_TRACE diff --git a/tests/systemc/sc_log/test01/golden/test01.log b/tests/systemc/sc_log/test01/golden/test01.log new file mode 100644 index 000000000..5820952a4 --- /dev/null +++ b/tests/systemc/sc_log/test01/golden/test01.log @@ -0,0 +1,48 @@ +SystemC Simulation +TEST REPORT: DEBUG : [sc_main] Global Warning from sc_main line:221 +TEST REPORT: WARN : [quiet] Global Warning from sc_main line:222 +TEST REPORT: WARN : [GlobalLogger] My Global Warn from sc_main line:224 +0 is log_level CRITICAL +50 is log_level WARN +100 is log_level WARN +150 is log_level INFO +200 is log_level INFO +250 is log_level DEBUG +300 is log_level DEBUG +350 is log_level TRACE +400 is log_level TRACE +450 is log_level TRACE +Test string based handler +TEST REPORT: CRITICAL : [sc_log_test] CRITICAL line:233 +TEST REPORT: WARN : [sc_log_test] WARN line:233 +test FMT string +TEST REPORT: WARN : [SystemC] Testing FMT Hello world line:237 +construct module +TEST REPORT: WARN : [MyMod] HERE line:49 +TEST REPORT: WARN : [GlobalLogger] Global Warning line:50 +TEST REPORT: CRITICAL : [MyMod] Log to name() (at level CRITICAL) line:53 +TEST REPORT: CRITICAL : [My Tag] Log to My Tag (at level CRITICAL) line:54 +TEST REPORT: CRITICAL : [MyMod] Log to default () (at level CRITICAL) line:55 +TEST REPORT: CRITICAL : [test_handler] Log to test_handler (at level CRITICAL) line:56 +TEST REPORT: CRITICAL : [My TST Tag] Log using () to My TH Tag (at level CRITICAL) line:57 +TEST REPORT: WARN : [MyMod] Log to name() (at level WARN) line:53 +TEST REPORT: WARN : [My Tag] Log to My Tag (at level WARN) line:54 +TEST REPORT: WARN : [MyMod] Log to default () (at level WARN) line:55 +TEST REPORT: WARN : [test_handler] Log to test_handler (at level WARN) line:56 +TEST REPORT: WARN : [My TST Tag] Log using () to My TH Tag (at level WARN) line:57 +TEST REPORT: INFO : [MyMod] Log to name() (at level INFO) line:53 +TEST REPORT: INFO : [My Tag] Log to My Tag (at level INFO) line:54 +TEST REPORT: INFO : [MyMod] Log to default () (at level INFO) line:55 +TEST REPORT: INFO : [test_handler] Log to test_handler (at level INFO) line:56 +TEST REPORT: INFO : [My TST Tag] Log using () to My TH Tag (at level INFO) line:57 +TEST REPORT: DEBUG : [MyMod] Log to name() (at level DEBUG) line:53 +TEST REPORT: DEBUG : [My Tag] Log to My Tag (at level DEBUG) line:54 +TEST REPORT: DEBUG : [MyMod] Log to default () (at level DEBUG) line:55 +TEST REPORT: TRACE : [MyMod] Log to name() (at level TRACE) line:53 +TEST REPORT: TRACE : [My Tag] Log to My Tag (at level TRACE) line:54 +TEST REPORT: TRACE : [MyMod] Log to default () (at level TRACE) line:55 +TEST REPORT: CRITICAL : [MyMod] SC_CRITICAL line:61 +TEST REPORT: WARN : [MyMod] SC_WARN line:62 +TEST REPORT: INFO : [MyMod] SC_INFO line:63 +TEST REPORT: DEBUG : [MyMod] SC_DEBUG line:64 +TEST REPORT: TRACE : [MyMod] SC_TRACE line:65 diff --git a/tests/systemc/sc_log/test01.cpp b/tests/systemc/sc_log/test01/test01.cpp similarity index 62% rename from tests/systemc/sc_log/test01.cpp rename to tests/systemc/sc_log/test01/test01.cpp index a9ba3b8d3..7c3e5f5f0 100644 --- a/tests/systemc/sc_log/test01.cpp +++ b/tests/systemc/sc_log/test01/test01.cpp @@ -36,27 +36,36 @@ *****************************************************************************/ #include "systemc.h" +#include +#include +#include #include +SC_LOG_HANDLE_STATIC(MY_GLOBAL_LOGGER, "GlobalLogger"); + SC_MODULE(mod_a) { - SC_LOG_HANDLE((TST), "test_handler"); + SC_LOG_HANDLE(TST, "test_handler"); SC_CTOR(mod_a) { + SC_WARN() << "HERE"; + SC_WARN(MY_GLOBAL_LOGGER)("Global Warning"); for (auto l : sc_core::log_level_map) { auto i = l.first; - SC_LOG_AT(i, name()) << " Log to name()" << " (at level "<< i<<")"; - SC_LOG_AT(i, ()) << " Log to default ()" << " (at level "<< i<<")"; - SC_LOG_AT(i, (TST)) << " Log to test_handler" << " (at level "<< i<<")"; + SC_LOG_AT(i, name()) << " Log to name()" << " (at level " << i << ")"; + SC_LOG_AT(i, "My Tag") << " Log to My Tag" << " (at level " << i << ")"; + SC_LOG_AT(i) << " Log to default ()" << " (at level " << i << ")"; + SC_LOG_AT(i, TST) << " Log to test_handler" << " (at level " << i << ")"; + SC_LOG_AT(i, TST, "My TST Tag") + << " Log using () to My TH Tag" << " (at level " << i << ")"; } - SC_CRITICAL(()) << "SC_CRITICAL"; - SC_WARN(()) << "SC_WARN"; - SC_INFO(()) << "SC_INFO"; - SC_DEBUG(()) << "SC_DEBUG"; - SC_TRACE(()) << "SC_TRACE"; + SC_CRITICAL() << "SC_CRITICAL"; + SC_WARN() << "SC_WARN"; + SC_INFO() << "SC_INFO"; + SC_DEBUG() << "SC_DEBUG"; + SC_TRACE() << "SC_TRACE"; } }; - /********************************************************** * The mechanism by which SC_LOG macros are enabled and * disabled is implementation-defined. @@ -68,7 +77,7 @@ SC_MODULE(mod_a) { * Non-standard API: * * Install a callback used to determine the effective log - * level for a given (sc_name, typ_name) pair. + * level for a given logger cache and local_tag. * * The cache parameter may be used to remember a computed * level. Once set, the function will not be re-called. @@ -80,8 +89,7 @@ SC_MODULE(mod_a) { * sc_core::sc_log_logger_cache &logger, * const char *file, * int line, - * std::string_view sc_name, - * const char *typ_name + * std::string_view local_tag * ) * > fn * ); @@ -90,7 +98,7 @@ SC_MODULE(mod_a) { * Non-standard API: * * Query the current log verbosity for the given cache and - * identifiers. + * local_tag. * * If no callback has been installed, the implementation * will fall back to the global report verbosity. @@ -100,8 +108,7 @@ SC_MODULE(mod_a) { * sc_core::sc_log_logger_cache &logger, * const char *file, * int line, - * std::string_view sc_name, - * const char *typ_name); + * std::string_view local_tag); * ); * * Together with the sc_log_logger_cache @@ -120,34 +127,69 @@ SC_MODULE(mod_a) { * part of the standard, and tool environments may differ. **********************************************************/ - class scp_logger_test { - std::unordered_set loggers; + + std::unordered_map lut; + + // BKDR hash algorithm + auto char_hash(std::string_view str) -> uint64_t { + constexpr unsigned int seed = 131; + uint64_t hash = 0; + for (char c : str) { + hash = (hash * seed) + static_cast(c); + } + return hash; + } + + std::unordered_set loggers; sc_core::sc_log_level operator()(struct sc_core::sc_log_logger_cache &logger, - const char *file, - int line, - std::string_view scname, - const char *tname - ) { + const char *file, int line, + std::string_view local_tag) { loggers.insert(&logger); - if (logger.features.size() && logger.features[0] == "test_handler") { + + if (logger.tag.empty() && logger.scname.empty()) { + /* This must be a global logger lets base our decision on the local_tag + * And we wont 'cache' the result in the logger, because we need to check + * the local_tag each time + */ + + auto k = char_hash(local_tag); + auto it = lut.find(k); + if (it != lut.end()) { + return it->second; + } + + sc_core::sc_log_level lvl = sc_core::sc_log_level::TRACE; + if (local_tag == "quiet") + lvl = sc_core::sc_log_level::WARN; + if (local_tag == "sc_main") + lvl = sc_core::sc_log_level::DEBUG; + if (local_tag == "sc_log_test") + lvl = sc_core::sc_log_level::WARN; + + lut[k] = lvl; + return lvl; + } + + if (logger.tag == "test_handler") { return sc_core::sc_log_level::INFO; } - if (scname == "sc_log_test") { - /* test every time, and dont cache */ + if (logger.tag == "GlobalLogger") { return sc_core::sc_log_level::WARN; } /* Cache this one which will catch the normal SCMOD case for mod_a */ logger.level = sc_core::sc_log_level::TRACE; return sc_core::sc_log_level::TRACE; } + public: scp_logger_test() { std::function - fn = [&](sc_core::sc_log_logger_cache &logger, const char *file, int line, std::string_view sc_name, - const char *t_name) -> sc_core::sc_log_level { - return operator()(logger, file, line, sc_name, t_name); + const char *, int, std::string_view)> + fn = [&](sc_core::sc_log_logger_cache &logger, const char *file, + int line, + std::string_view local_tag) -> sc_core::sc_log_level { + return operator()(logger, file, line, local_tag); }; ::sc_core::sc_log_impl::sc_set_log_verbosity_fn(fn); ::sc_core::sc_report_handler::set_verbosity_level( @@ -165,15 +207,24 @@ class scp_logger_test { static scp_logger_test test_logger_handler; -void report_handler(const sc_core::sc_report& rep, const sc_core::sc_actions& actions) -{ - cout << "TEST REPORT: "< +#include + +// Test 1: Global static logger handle with tag +// Note: The default global SC_LOG_LOG_LEVEL_CACHE is already declared in sc_log.h +SC_LOG_HANDLE_STATIC(GLOBAL_LOGGER_WITH_TAG, "GlobalTag"); + +// Test 2: Module with default logger (no tag) +SC_MODULE(test_module_default) { + SC_LOG_HANDLE(); + + SC_CTOR(test_module_default) { + cout << "\n=== Test Module with Default Logger ===" << endl; + + // Test all log levels with stream syntax + SC_CRITICAL() << "CRITICAL: Default logger, stream syntax"; + SC_WARN() << "WARN: Default logger, stream syntax"; + SC_INFO() << "INFO: Default logger, stream syntax"; + SC_DEBUG() << "DEBUG: Default logger, stream syntax"; + SC_TRACE() << "TRACE: Default logger, stream syntax"; + + // Test format string syntax + SC_WARN()("WARN: Default logger, format syntax with arg: {}", 42); + SC_INFO()("INFO: Default logger, format syntax with multiple args: {} {}", "hello", "world"); + } +}; + +// Test 3: Module with tagged logger +SC_MODULE(test_module_tagged) { + SC_LOG_HANDLE("ModuleTag"); + + SC_CTOR(test_module_tagged) { + cout << "\n=== Test Module with Tagged Logger ===" << endl; + + SC_CRITICAL() << "CRITICAL: Tagged logger"; + SC_WARN() << "WARN: Tagged logger"; + SC_INFO() << "INFO: Tagged logger"; + SC_DEBUG() << "DEBUG: Tagged logger"; + SC_TRACE() << "TRACE: Tagged logger"; + + SC_INFO()("INFO: Tagged logger with format: {}", 123); + } +}; + +// Test 4: Module with multiple named loggers +SC_MODULE(test_module_multi_logger) { + SC_LOG_HANDLE(logger1, "Logger1Tag"); + SC_LOG_HANDLE(logger2, "Logger2Tag"); + SC_LOG_HANDLE(); // Default logger + + SC_CTOR(test_module_multi_logger) { + cout << "\n=== Test Module with Multiple Named Loggers ===" << endl; + + // Test logger1 + SC_INFO(logger1) << "INFO: Using logger1"; + SC_WARN(logger1)("WARN: Using logger1 with format: {}", "test"); + + // Test logger2 + SC_INFO(logger2) << "INFO: Using logger2"; + SC_DEBUG(logger2)("DEBUG: Using logger2 with format: {}", 456); + + // Test default logger + SC_INFO() << "INFO: Using default logger"; + SC_TRACE()("TRACE: Using default logger with format"); + } +}; + +// Test 5: Module with vector of loggers +SC_MODULE(test_module_vector) { + SC_LOG_HANDLE_VECTOR(logger_vec); + + SC_CTOR(test_module_vector) { + cout << "\n=== Test Module with Vector of Loggers ===" << endl; + + // Populate vector + SC_LOG_HANDLE_VECTOR_PUSH_BACK(logger_vec, "VecLogger0"); + SC_LOG_HANDLE_VECTOR_PUSH_BACK(logger_vec, "VecLogger1"); + SC_LOG_HANDLE_VECTOR_PUSH_BACK(logger_vec, "VecLogger2"); + + // Use loggers from vector + for (size_t i = 0; i < logger_vec.size(); i++) { + SC_INFO(logger_vec[i]) << "INFO: Vector logger " << i; + SC_WARN(logger_vec[i])("WARN: Vector logger {} with format", i); + } + } +}; + +// Test 6: SC_LOG_AT with different argument combinations +SC_MODULE(test_module_log_at) { + SC_LOG_HANDLE(my_logger, "LogAtTag"); + + SC_CTOR(test_module_log_at) { + cout << "\n=== Test SC_LOG_AT Variants ===" << endl; + + // SC_LOG_AT with no additional args (uses default logger) + SC_LOG_AT(sc_core::sc_log_level::INFO) << "SC_LOG_AT: No args, stream syntax"; + SC_LOG_AT(sc_core::sc_log_level::WARN)("SC_LOG_AT: No args, format syntax: {}", 1); + + // SC_LOG_AT with logger handle + SC_LOG_AT(sc_core::sc_log_level::INFO, my_logger) << "SC_LOG_AT: With logger handle"; + SC_LOG_AT(sc_core::sc_log_level::DEBUG, my_logger)("SC_LOG_AT: With logger handle, format: {}", 2); + + // SC_LOG_AT with explicit tag string + SC_LOG_AT(sc_core::sc_log_level::INFO, "ExplicitTag") << "SC_LOG_AT: With explicit tag"; + SC_LOG_AT(sc_core::sc_log_level::TRACE, "ExplicitTag")("SC_LOG_AT: With explicit tag, format: {}", 3); + + // SC_LOG_AT with logger and tag + SC_LOG_AT(sc_core::sc_log_level::WARN, my_logger, "OverrideTag") << "SC_LOG_AT: Logger + override tag"; + SC_LOG_AT(sc_core::sc_log_level::INFO, my_logger, "OverrideTag")("SC_LOG_AT: Logger + override tag, format: {}", 4); + } +}; + +// Test 7: Using name() as tag +SC_MODULE(test_module_name_tag) { + SC_LOG_HANDLE(); + + SC_CTOR(test_module_name_tag) { + cout << "\n=== Test Using name() as Tag ===" << endl; + + SC_LOG_AT(sc_core::sc_log_level::INFO, name()) << "Using name() as tag"; + SC_LOG_AT(sc_core::sc_log_level::WARN, name())("Using name() as tag with format: {}", name()); + } +}; + +// Test 8: Global logger usage +void test_global_loggers() { + cout << "\n=== Test Global Static Loggers ===" << endl; + + SC_INFO() << "INFO: Global default logger (no tag)"; + SC_WARN()("WARN: Global default logger, format: {}", "test"); + + SC_INFO(GLOBAL_LOGGER_WITH_TAG) << "INFO: Global logger with tag"; + SC_DEBUG(GLOBAL_LOGGER_WITH_TAG)("DEBUG: Global logger with tag, format: {}", 789); +} + +// Test 9: Tag-based logging (using string literals as tags) +void test_tag_based_logging() { + cout << "\n=== Test Tag-Based Logging ===" << endl; + + SC_INFO("Tag1") << "INFO: Using Tag1"; + SC_WARN("Tag2")("WARN: Using Tag2 with format: {}", "value"); + SC_DEBUG("Tag3") << "DEBUG: Using Tag3"; + SC_TRACE("Tag4")("TRACE: Using Tag4 with format: {}", 999); +} + +// Test 10: All log levels +void test_all_log_levels() { + cout << "\n=== Test All Log Levels ===" << endl; + + SC_CRITICAL("LevelTest") << "CRITICAL level test"; + SC_WARN("LevelTest") << "WARN level test"; + SC_INFO("LevelTest") << "INFO level test"; + SC_DEBUG("LevelTest") << "DEBUG level test"; + SC_TRACE("LevelTest") << "TRACE level test"; + + SC_CRITICAL("LevelTest")("CRITICAL level test with format: {}", 1); + SC_WARN("LevelTest")("WARN level test with format: {}", 2); + SC_INFO("LevelTest")("INFO level test with format: {}", 3); + SC_DEBUG("LevelTest")("DEBUG level test with format: {}", 4); + SC_TRACE("LevelTest")("TRACE level test with format: {}", 5); +} + +// Test 11: Complex format strings +void test_complex_formats() { + cout << "\n=== Test Complex Format Strings ===" << endl; + + SC_INFO("FormatTest")("Multiple args: {}, {}, {}", 1, 2, 3); + SC_INFO("FormatTest")("Mixed types: int={}, str={}, float={:.2f}", 42, "hello", 3.14159); + SC_INFO("FormatTest")("Nested braces: {{}} and value: {}", 123); + SC_INFO("FormatTest")("Empty format string"); +} + +// Simple verbosity control for testing +class test_verbosity_controller { + std::map tag_levels; + +public: + test_verbosity_controller() { + // Set different levels for different tags + tag_levels["GlobalTag"] = sc_core::sc_log_level::INFO; + tag_levels["ModuleTag"] = sc_core::sc_log_level::DEBUG; + tag_levels["Logger1Tag"] = sc_core::sc_log_level::WARN; + tag_levels["Logger2Tag"] = sc_core::sc_log_level::TRACE; + tag_levels["VecLogger0"] = sc_core::sc_log_level::INFO; + tag_levels["VecLogger1"] = sc_core::sc_log_level::WARN; + tag_levels["VecLogger2"] = sc_core::sc_log_level::DEBUG; + tag_levels["LogAtTag"] = sc_core::sc_log_level::TRACE; + tag_levels["ExplicitTag"] = sc_core::sc_log_level::DEBUG; + tag_levels["OverrideTag"] = sc_core::sc_log_level::INFO; + tag_levels["Tag1"] = sc_core::sc_log_level::INFO; + tag_levels["Tag2"] = sc_core::sc_log_level::WARN; + tag_levels["Tag3"] = sc_core::sc_log_level::DEBUG; + tag_levels["Tag4"] = sc_core::sc_log_level::TRACE; + tag_levels["LevelTest"] = sc_core::sc_log_level::TRACE; + tag_levels["FormatTest"] = sc_core::sc_log_level::INFO; + + std::function + fn = [&](sc_core::sc_log_logger_cache &logger, const char *file, + int line, + std::string_view local_tag) -> sc_core::sc_log_level { + // Check if we have a specific level for this tag + std::string tag_str; + if (!logger.tag.empty()) { + tag_str = std::string(logger.tag); + } else if (!local_tag.empty()) { + tag_str = std::string(local_tag); + } else if (!logger.scname.empty()) { + tag_str = std::string(logger.scname); + } + + auto it = tag_levels.find(tag_str); + if (it != tag_levels.end()) { + if (logger.level == sc_core::sc_log_level::UNSET) { + logger.level = it->second; + } + return it->second; + } + + // Default to TRACE for everything else + if (logger.level == sc_core::sc_log_level::UNSET) { + logger.level = sc_core::sc_log_level::TRACE; + } + return sc_core::sc_log_level::TRACE; + }; + + ::sc_core::sc_log_impl::sc_set_log_verbosity_fn(fn); + ::sc_core::sc_report_handler::set_verbosity_level(sc_core::SC_DEBUG); + } +}; + +void report_handler(const sc_core::sc_report &rep, + const sc_core::sc_actions &actions) { + cout << "[" << sc_core::as_log(rep.get_verbosity()) << "] " + << rep.get_msg_type() << ": " << rep.get_msg() << endl; +} + +int sc_main(int, char *[]) { + ::sc_core::sc_report_handler::set_verbosity_level(sc_core::SC_DEBUG); + ::sc_core::sc_report_handler::set_handler(report_handler); + + test_verbosity_controller verbosity_ctrl; + + cout << "========================================" << endl; + cout << "SC_LOG API Comprehensive Test (test02)" << endl; + cout << "========================================" << endl; + + // Test global loggers + test_global_loggers(); + + // Test tag-based logging + test_tag_based_logging(); + + // Test all log levels + test_all_log_levels(); + + // Test complex format strings + test_complex_formats(); + + // Instantiate test modules + test_module_default mod_default("mod_default"); + test_module_tagged mod_tagged("mod_tagged"); + test_module_multi_logger mod_multi("mod_multi"); + test_module_vector mod_vector("mod_vector"); + test_module_log_at mod_log_at("mod_log_at"); + test_module_name_tag mod_name_tag("mod_name_tag"); + + cout << "\n========================================" << endl; + cout << "All tests completed successfully!" << endl; + cout << "========================================" << endl; + + return 0; +} From de4e722c45c01a3f4d89f6e32cb2670e5b5ba779 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Fri, 17 Apr 2026 10:46:40 +0200 Subject: [PATCH 16/50] SC_LOG: bug fixes, std::string ownership, internal improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/sysc/log/sc_log.cpp | 16 ++++++++++++--- src/sysc/log/sc_log.h | 18 ++++++++++++----- src/sysc/log/sc_log_types.h | 40 ++++++++++++++++++++++++++++--------- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/sysc/log/sc_log.cpp b/src/sysc/log/sc_log.cpp index c194b5e2d..5b1aee3d6 100644 --- a/src/sysc/log/sc_log.cpp +++ b/src/sysc/log/sc_log.cpp @@ -39,8 +39,14 @@ const std::map log_level_map = { {sc_log_level::TRACE, "TRACE"} }; +static thread_local sc_log_logger_cache* s_current = nullptr; + +sc_log_logger_cache* sc_log_logger_cache::get_current() { return s_current; } +void sc_log_logger_cache::set_current(sc_log_logger_cache* p) { s_current = p; } + sc_log_level sc_log_logger_cache::get_log_verbosity_cached( const char *file, int line, std::string_view local_tag) { + s_current = this; if (level != sc_log_level::UNSET) { return level; } @@ -48,14 +54,18 @@ sc_log_level sc_log_logger_cache::get_log_verbosity_cached( return sc_log_impl::sc_get_log_verbosity(*this, file, line, local_tag); } +void sc_log_logger_cache::set_tag(std::string new_tag) { + tag = std::move(new_tag); + level = sc_log_level::UNSET; +} + } // namespace sc_core // Global default logger with empty tag (in global namespace for proper name // shadowing). This logger is used when no specific logger handle is provided. -// Note: Using string_view{} for empty views and nullptr for typename_str. sc_core::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE{ sc_core::sc_log_level::UNSET, // level - std::string_view{}, // tag (empty) - std::string_view{}, // scname (empty) + {}, // tag (empty) + {}, // scname (empty) nullptr // typename_str (no type info) }; diff --git a/src/sysc/log/sc_log.h b/src/sysc/log/sc_log.h index d05cd4ede..d177b1e29 100644 --- a/src/sysc/log/sc_log.h +++ b/src/sysc/log/sc_log.h @@ -71,7 +71,14 @@ extern sc_core::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE; // macro When used without parentheses, it's the empty string; with parentheses, // it calls std::format static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; +#if __has_include() +#include +#define SC_LOG_PRIV__FMT_EMPTY_STR(...) fmt::format(__VA_ARGS__) +#elif defined(__cpp_lib_format) #define SC_LOG_PRIV__FMT_EMPTY_STR(...) std::format(__VA_ARGS__) +#else +#define SC_LOG_PRIV__FMT_EMPTY_STR(...) "" +#endif // Internal verbosity check variants (used by public SC_LOG_AT macro) #define SC_LOG_PRIV__VBSTY_CHECK0(lvl) \ @@ -85,7 +92,7 @@ static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; return ((x.level >= (lvl)) && \ (x.get_log_verbosity_cached(__FILE__, __LINE__) >= (lvl))); \ } else { \ - return SC_LOG_PRIV__VBSTY_CHECK2(lvl, SC_LOG_LOG_LEVEL_CACHE, x); \ + return SC_LOG_PRIV__VBSTY_CHECK2(lvl, SC_LOG_LOG_LEVEL_CACHE_GLOBAL, x); \ } \ }(arg1)) @@ -98,13 +105,13 @@ static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; // Internal tag extraction variants (used by public SC_LOG_MSG macro) #define SC_LOG_PRIV__GET_TAG0() \ - (SC_LOG_LOG_LEVEL_CACHE.tag.empty() ? SC_LOG_LOG_LEVEL_CACHE.scname.data() \ - : SC_LOG_LOG_LEVEL_CACHE.tag.data()) + (SC_LOG_LOG_LEVEL_CACHE.tag.empty() ? SC_LOG_LOG_LEVEL_CACHE.scname.c_str() \ + : SC_LOG_LOG_LEVEL_CACHE.tag.c_str()) #define SC_LOG_PRIV__GET_TAG1(arg1) \ ([&](auto &&x) -> const char * { \ if constexpr (SC_LOG_PRIV__IS_LOGGER_HANDLE(x)) { \ - return (x.tag.empty() ? x.scname.data() : x.tag.data()); \ + return (x.tag.empty() ? x.scname.c_str() : x.tag.c_str()); \ } else { \ return x; \ } \ @@ -184,7 +191,8 @@ static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; */ #define SC_LOG_HANDLE_VECTOR_PUSH_BACK(NAME, tag_str) \ SC_LOG_PRIV__HANDLE_NAME(NAME).push_back( \ - {sc_core::sc_log_level::UNSET, "", tag_str}) + sc_core::sc_log_handle_factory::make( \ + sc_core::sc_log_level::UNSET, tag_str, this)) /** * SC_LOG_AT - Log a message at a specific level diff --git a/src/sysc/log/sc_log_types.h b/src/sysc/log/sc_log_types.h index d6984d971..722620de5 100644 --- a/src/sysc/log/sc_log_types.h +++ b/src/sysc/log/sc_log_types.h @@ -166,8 +166,8 @@ struct sc_log_impl; */ struct sc_log_logger_cache { sc_log_level level = sc_log_level::UNSET; - std::string_view tag{}; // Logger tag/identifier - std::string_view scname{}; // Captured sc_object name (from this->name()) + std::string tag{}; // Logger tag/identifier (owns the string) + std::string scname{}; // Captured sc_object name (owns the string) const char* typename_str = nullptr; // Captured type name (from typeid(*this).name()) /** @@ -183,6 +183,27 @@ struct sc_log_logger_cache { */ sc_log_level get_log_verbosity_cached(const char *file, int line, std::string_view local_tag = {}); + + /** + * @brief Set the tag to a runtime-computed string. + * + * The cached level is reset so the verbosity function re-evaluates + * with the new tag on the next log statement. + * + * @param new_tag the new tag string + */ + void set_tag(std::string new_tag); + + /// Get/set the logger cache that most recently performed a verbosity + /// check on this thread. Used by the report handler to access + /// scname/tag/typename. + /// + /// Lifecycle: set by get_log_verbosity_cached() (called during the + /// SC_LOG verbosity check), cleared by ~sc_logger() (after the report + /// handler has run). Accessor functions ensure correct linkage across + /// shared library boundaries. + static sc_log_logger_cache* get_current(); + static void set_current(sc_log_logger_cache* p); }; /** @@ -193,8 +214,6 @@ struct sc_log_logger_cache { * default constructor, so the owning object pointer must be provided here (when * available). * - * The factory returns string_view for tag and scname (which reference string - * literals or existing strings), and const char* for typename_str (from typeid). */ struct sc_log_handle_factory { template @@ -204,8 +223,8 @@ struct sc_log_handle_factory { const char *t = typeid(*p).name(); return sc_log_logger_cache{ lvl, - tag_str ? std::string_view(tag_str) : std::string_view(), - n ? std::string_view(n) : std::string_view(), + tag_str ? std::string(tag_str) : std::string(), + n ? std::string(n) : std::string(), t // typeid().name() returns const char* with static storage }; } @@ -214,8 +233,8 @@ struct sc_log_handle_factory { const char *tag_str) { return sc_log_logger_cache{ lvl, - tag_str ? std::string_view(tag_str) : std::string_view(), - std::string_view(), + tag_str ? std::string(tag_str) : std::string(), + std::string(), nullptr // No type information for static loggers }; } @@ -250,6 +269,7 @@ struct sc_logger { sc_log_level verbosity = sc_core::sc_log_level::INFO) : t(nullptr), file(file), line(line), level(verbosity) {} + sc_logger() = delete; sc_logger(const sc_logger &) = delete; @@ -275,9 +295,10 @@ struct sc_logger { sc_core::SC_STOP | sc_core::SC_ABORT)); } ::sc_core::sc_report_handler::report( - SEVERITY, t ? t : "SystemC", os.str().c_str(), + SEVERITY, (t && *t) ? t : "SystemC", os.str().c_str(), static_cast(level), file, line); sc_core::sc_report_handler::set_actions(SEVERITY, old); + sc_log_logger_cache::set_current(nullptr); } /** * @fn sc_logger& type() @@ -336,6 +357,7 @@ struct sc_logger { // underscores are generally reserved, this pattern (_m_*) is safe as it doesn't // conflict with reserved patterns (__* or _Capital*). #define SC_LOG_LOG_LEVEL_CACHE _m_sc_log_log_level_cache_ +#define SC_LOG_LOG_LEVEL_CACHE_GLOBAL ::_m_sc_log_log_level_cache_ /** @} */ // end of sc_log #endif /* _SC_LOG_TYPES_H_ */ From 1fccfb10766e03e5d875d886a0ef3d5313ffbef9 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Fri, 17 Apr 2026 21:55:50 +0200 Subject: [PATCH 17/50] SC_LOG: std::format/fmt detection, missing includes - CMake: detect std::format support at configure time; when not available, find_package(fmt) as fallback - Guard / includes with __has_include; prefer std::format (__cpp_lib_format), fall back to fmt (FMT_VERSION) - Add missing , , to sc_log_types.h (transitively included on macOS but not on Linux) Signed-off-by: Mark Burton --- src/CMakeLists.txt | 24 ++++++++++++++++++++---- src/sysc/log/sc_log.h | 15 +++++++++------ src/sysc/log/sc_log_types.h | 3 +++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 758701176..f58c796d7 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,14 +46,30 @@ cmake_minimum_required(VERSION 3.16...3.31) option(SYSTEMC_UNITY_BUILD "Enable unity build" OFF) +# Detect format string support: prefer std::format (C++20), fall back to fmt. +include(CheckCXXSourceCompiles) +set(CMAKE_REQUIRED_FLAGS "") +set(CMAKE_CXX_STANDARD 20) +check_cxx_source_compiles(" + #include + int main() { auto s = std::format(\"{}\", 42); return 0; } +" HAS_STD_FORMAT) +unset(CMAKE_CXX_STANDARD) + +if(NOT HAS_STD_FORMAT) + find_package(fmt QUIET) +endif() + function(add_systemc_library libName scBuildDefine) add_library(${libName} ${ARGN}) - target_compile_features( - ${libName} - PUBLIC - cxx_std_20) + target_compile_features(${libName} PUBLIC cxx_std_20) + + if(NOT HAS_STD_FORMAT AND fmt_FOUND) + target_link_libraries(${libName} PUBLIC fmt::fmt) + target_compile_definitions(${libName} PUBLIC FMT_SHARED) + endif() target_compile_definitions ( ${libName} diff --git a/src/sysc/log/sc_log.h b/src/sysc/log/sc_log.h index d177b1e29..73cffd162 100644 --- a/src/sysc/log/sc_log.h +++ b/src/sysc/log/sc_log.h @@ -27,7 +27,11 @@ #include #include +#if __has_include() #include +#elif __has_include() +#include +#endif #include #include #include @@ -68,14 +72,13 @@ extern sc_core::sc_log_logger_cache SC_LOG_LOG_LEVEL_CACHE; std::is_same_v, sc_core::sc_log_logger_cache> // Note: SC_LOG_PRIV__FMT_EMPTY_STR is both a const char* and a function-like -// macro When used without parentheses, it's the empty string; with parentheses, -// it calls std::format +// macro. Without parentheses it's the empty string; with parentheses it calls +// std::format (C++20, preferred) or fmt::format (fallback). static const char *SC_LOG_PRIV__FMT_EMPTY_STR = ""; -#if __has_include() -#include -#define SC_LOG_PRIV__FMT_EMPTY_STR(...) fmt::format(__VA_ARGS__) -#elif defined(__cpp_lib_format) +#if defined(__cpp_lib_format) #define SC_LOG_PRIV__FMT_EMPTY_STR(...) std::format(__VA_ARGS__) +#elif defined(FMT_VERSION) +#define SC_LOG_PRIV__FMT_EMPTY_STR(...) fmt::format(__VA_ARGS__) #else #define SC_LOG_PRIV__FMT_EMPTY_STR(...) "" #endif diff --git a/src/sysc/log/sc_log_types.h b/src/sysc/log/sc_log_types.h index 722620de5..74f7c96f3 100644 --- a/src/sysc/log/sc_log_types.h +++ b/src/sysc/log/sc_log_types.h @@ -26,9 +26,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include From 97f287ebeaf9a19bf9443971eb7dd8dd7359e523 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Sun, 1 Mar 2026 05:32:22 -0500 Subject: [PATCH 18/50] Andy Goodrich: use separate names for thread_local vpools. --- src/sysc/datatypes/int/sc_int_base.h | 14 ++++---------- src/sysc/datatypes/int/sc_signed.h | 10 +++++----- src/sysc/datatypes/int/sc_uint_base.h | 8 ++++---- src/sysc/datatypes/int/sc_unsigned.h | 12 ++++++------ src/sysc/datatypes/misc/sc_concatref.h | 8 ++++---- 5 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/sysc/datatypes/int/sc_int_base.h b/src/sysc/datatypes/int/sc_int_base.h index 7a1b9d6eb..ce2d98148 100644 --- a/src/sysc/datatypes/int/sc_int_base.h +++ b/src/sysc/datatypes/int/sc_int_base.h @@ -105,12 +105,6 @@ class sc_fxnum_fast; } // namespace sc_dt -// extern template instantiations -namespace sc_core { -SC_API_TEMPLATE_DECL_ sc_vpool; -SC_API_TEMPLATE_DECL_ sc_vpool; -} // namespace sc_core - namespace sc_dt { extern SC_API const uint_type mask_int[SC_INTWIDTH][SC_INTWIDTH]; @@ -752,8 +746,8 @@ class SC_API sc_int_base : public sc_value_base sc_int_bitref* temporary_bitref() const { - static sc_core::sc_vpool pool(9); - return pool.allocate(); + thread_local sc_core::sc_vpool sc_int_bit_ref_pool(9); + return sc_int_bit_ref_pool.allocate(); } @@ -767,8 +761,8 @@ class SC_API sc_int_base : public sc_value_base sc_int_subref* temporary_subref() const { - static sc_core::sc_vpool pool(9); - return pool.allocate(); + thread_local sc_core::sc_vpool sc_int_subref_pool(9); + return sc_int_subref_pool.allocate(); } diff --git a/src/sysc/datatypes/int/sc_signed.h b/src/sysc/datatypes/int/sc_signed.h index 71f19a022..ebb569269 100644 --- a/src/sysc/datatypes/int/sc_signed.h +++ b/src/sysc/datatypes/int/sc_signed.h @@ -667,8 +667,8 @@ class SC_API sc_signed : public sc_value_base sc_signed_bitref* temporary_bitref() const { - static sc_core::sc_vpool pool(9); - return pool.allocate(); + thread_local sc_core::sc_vpool sc_signed_bitref_pool(9); + return sc_signed_bitref_pool.allocate(); } sc_signed_bitref& operator [] ( int i ) @@ -729,14 +729,14 @@ class SC_API sc_signed : public sc_value_base sc_signed_subref* temporary_subref() const { - static sc_core::sc_vpool pool(9); - return pool.allocate(); + thread_local sc_core::sc_vpool sc_signed_subref_pool(9); + return sc_signed_subref_pool.allocate(); } sc_signed_subref& range( int i, int j ) { check_range( i, j ); - sc_signed_subref* result_p = temporary_subref(); + sc_signed_subref* result_p = temporary_subref(); result_p->initialize( this, i, j ); return *result_p; } diff --git a/src/sysc/datatypes/int/sc_uint_base.h b/src/sysc/datatypes/int/sc_uint_base.h index 3cf26b5e6..c3ee44b16 100644 --- a/src/sysc/datatypes/int/sc_uint_base.h +++ b/src/sysc/datatypes/int/sc_uint_base.h @@ -720,8 +720,8 @@ class SC_API sc_uint_base : public sc_value_base sc_uint_bitref* temporary_bitref() const { - static sc_core::sc_vpool pool(9); - return pool.allocate(); + thread_local sc_core::sc_vpool sc_uint_bitref_pool(9); + return sc_uint_bitref_pool.allocate(); } @@ -735,8 +735,8 @@ class SC_API sc_uint_base : public sc_value_base sc_uint_subref* temporary_subref() const { - static sc_core::sc_vpool pool(9); - return pool.allocate(); + thread_local sc_core::sc_vpool sc_uint_subref_pool(9); + return sc_uint_subref_pool.allocate(); } diff --git a/src/sysc/datatypes/int/sc_unsigned.h b/src/sysc/datatypes/int/sc_unsigned.h index 41606fc80..1fa8c1751 100644 --- a/src/sysc/datatypes/int/sc_unsigned.h +++ b/src/sysc/datatypes/int/sc_unsigned.h @@ -662,8 +662,8 @@ class SC_API sc_unsigned : public sc_value_base sc_unsigned_bitref* temporary_bitref() const { - static sc_core::sc_vpool pool(9); - return pool.allocate(); + thread_local sc_core::sc_vpool sc_unsigned_bit_ref_pool(9); + return sc_unsigned_bit_ref_pool.allocate(); } sc_unsigned_bitref& operator [] ( int i ) @@ -724,8 +724,8 @@ class SC_API sc_unsigned : public sc_value_base sc_unsigned_subref* temporary_subref() const { - static sc_core::sc_vpool pool(9); - return pool.allocate(); + thread_local sc_core::sc_vpool sc_unsigned_subref_pool(9); + return sc_unsigned_subref_pool.allocate(); } sc_unsigned_subref& range( int i, int j ) @@ -764,8 +764,8 @@ class SC_API sc_unsigned : public sc_value_base static sc_unsigned* temporary() { - static sc_core::sc_vpool pool(9); - return pool.allocate(); + thread_local sc_core::sc_vpool sc_unsigned_tempoerary_pool(9); + return sc_unsigned_tempoerary_pool.allocate(); } // explicit conversions diff --git a/src/sysc/datatypes/misc/sc_concatref.h b/src/sysc/datatypes/misc/sc_concatref.h index 7ef4923c2..2a3b05866 100644 --- a/src/sysc/datatypes/misc/sc_concatref.h +++ b/src/sysc/datatypes/misc/sc_concatref.h @@ -621,8 +621,8 @@ class SC_API sc_concat_bool : public sc_value_base static inline sc_concat_bool* allocate( bool v ) { - static sc_core::sc_vpool pool(9); - sc_concat_bool* result_p = pool.allocate(); + thread_local sc_core::sc_vpool sc_concat_bool_pool(9); + sc_concat_bool* result_p = sc_concat_bool_pool.allocate(); result_p->m_value = v; return result_p; } @@ -749,8 +749,8 @@ SC_CONCAT_BOOL_OP(<) static inline sc_dt::sc_concatref* temporary_concatref() { - static sc_core::sc_vpool pool(9); - sc_dt::sc_concatref* result_p = pool.allocate(); + thread_local sc_core::sc_vpool sc_concatref_pool(9); + sc_dt::sc_concatref* result_p = sc_concatref_pool.allocate(); return result_p; } From 4044b65563dd02129899bcb728b6535ecb569953 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Sun, 1 Mar 2026 17:29:15 -0500 Subject: [PATCH 19/50] Andy Goodrich: use thread_local on two temporary pools I missed. These temporaries only occur with SC_BIGINT_CONFIG_TEMPLATE_CLASS_HAS_NO_BASE_CLASS defined. --- src/sysc/datatypes/int/sc_signed.cpp | 7 ------- src/sysc/datatypes/int/sc_signed.h | 7 ++----- src/sysc/datatypes/int/sc_unsigned.cpp | 7 ------- src/sysc/datatypes/int/sc_unsigned.h | 12 +++--------- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/sysc/datatypes/int/sc_signed.cpp b/src/sysc/datatypes/int/sc_signed.cpp index 229ddefcf..010012b9b 100644 --- a/src/sysc/datatypes/int/sc_signed.cpp +++ b/src/sysc/datatypes/int/sc_signed.cpp @@ -1079,13 +1079,6 @@ sc_signed_subref::scan( ::std::istream& is ) *this = s.c_str(); } -#if defined(SC_BIGINT_CONFIG_TEMPLATE_CLASS_HAS_NO_BASE_CLASS) -// Temporary values: - -sc_signed sc_signed::m_temporaries[SC_SIGNED_TEMPS_N]; -size_t sc_signed::m_temporaries_i = 0; -#endif // defined(SC_BIGINT_CONFIG_TEMPLATE_CLASS_HAS_NO_BASE_CLASS) - } // namespace sc_dt // End of file. diff --git a/src/sysc/datatypes/int/sc_signed.h b/src/sysc/datatypes/int/sc_signed.h index ebb569269..091465203 100644 --- a/src/sysc/datatypes/int/sc_signed.h +++ b/src/sysc/datatypes/int/sc_signed.h @@ -1165,8 +1165,6 @@ class SC_API sc_signed : public sc_value_base #define SC_SIGNED_TEMPS_N (1 << 15) // SC_SIGNED_TEMPS_N must be a power of 2. public: // Temporary object support: - static sc_signed m_temporaries[SC_SIGNED_TEMPS_N]; - static size_t m_temporaries_i; // +-------------------------------------------------------------------------------------------- // |"allocate_temporary" @@ -1182,9 +1180,8 @@ class SC_API sc_signed : public sc_value_base // +-------------------------------------------------------------------------------------------- static inline sc_signed& allocate_temporary( int nb, sc_digit* digits_p ) { - - sc_signed* result_p = &m_temporaries[m_temporaries_i]; - m_temporaries_i = (m_temporaries_i + 1) & (SC_SIGNED_TEMPS_N-1); + thread_local sc_core::sc_vpool sc_signed_temporary_pool(SC_SIGNED_TEMPS_N); + sc_signed* result_p = sc_signed_temporary_pool.allocate(); result_p->digit = digits_p; result_p->nbits = num_bits(nb); result_p->ndigits = DIV_CEIL(result_p->nbits); diff --git a/src/sysc/datatypes/int/sc_unsigned.cpp b/src/sysc/datatypes/int/sc_unsigned.cpp index 08ee64525..d2a50e1fe 100644 --- a/src/sysc/datatypes/int/sc_unsigned.cpp +++ b/src/sysc/datatypes/int/sc_unsigned.cpp @@ -1058,13 +1058,6 @@ sc_unsigned_subref::scan( ::std::istream& is ) *this = s.c_str(); } -#if defined(SC_BIGINT_CONFIG_TEMPLATE_CLASS_HAS_NO_BASE_CLASS) -// Temporary values: - -sc_unsigned sc_unsigned::m_temporaries[SC_UNSIGNED_TEMPS_N]; -size_t sc_unsigned::m_temporaries_i = 0; -#endif // defined(SC_BIGINT_CONFIG_TEMPLATE_CLASS_HAS_NO_BASE_CLASS) - } // namespace sc_dt diff --git a/src/sysc/datatypes/int/sc_unsigned.h b/src/sysc/datatypes/int/sc_unsigned.h index 1fa8c1751..8c57c62a3 100644 --- a/src/sysc/datatypes/int/sc_unsigned.h +++ b/src/sysc/datatypes/int/sc_unsigned.h @@ -764,8 +764,8 @@ class SC_API sc_unsigned : public sc_value_base static sc_unsigned* temporary() { - thread_local sc_core::sc_vpool sc_unsigned_tempoerary_pool(9); - return sc_unsigned_tempoerary_pool.allocate(); + thread_local sc_core::sc_vpool sc_unsigned_temporary_pool(9); + return sc_unsigned_temporary_pool.allocate(); } // explicit conversions @@ -1152,11 +1152,6 @@ class SC_API sc_unsigned : public sc_value_base public: // Temporary object support: - #define SC_UNSIGNED_TEMPS_N (1 << 15) // SC_UNSIGNED_TEMPS_N must be a power of 2. - - static sc_unsigned m_temporaries[SC_UNSIGNED_TEMPS_N]; - static size_t m_temporaries_i; - // +-------------------------------------------------------------------------------------------- // |"allocate_temporary" // | @@ -1171,8 +1166,7 @@ class SC_API sc_unsigned : public sc_value_base // +-------------------------------------------------------------------------------------------- static inline sc_unsigned& allocate_temporary( int nb, sc_digit* digits_p ) { - sc_unsigned* result_p = &m_temporaries[m_temporaries_i]; - m_temporaries_i = (m_temporaries_i + 1) & (SC_UNSIGNED_TEMPS_N-1); + sc_unsigned* result_p = temporary(); result_p->digit = digits_p; result_p->nbits = num_bits(nb); result_p->ndigits = DIV_CEIL(result_p->nbits); From ff4a7a68be30494366b568fc5372e1ef67c9c773 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Sun, 1 Mar 2026 17:41:36 -0500 Subject: [PATCH 20/50] Andy Goodrich: be consistent in naming temporary pools. --- src/sysc/datatypes/int/sc_unsigned.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sysc/datatypes/int/sc_unsigned.h b/src/sysc/datatypes/int/sc_unsigned.h index 8c57c62a3..4a5ed2c49 100644 --- a/src/sysc/datatypes/int/sc_unsigned.h +++ b/src/sysc/datatypes/int/sc_unsigned.h @@ -662,8 +662,8 @@ class SC_API sc_unsigned : public sc_value_base sc_unsigned_bitref* temporary_bitref() const { - thread_local sc_core::sc_vpool sc_unsigned_bit_ref_pool(9); - return sc_unsigned_bit_ref_pool.allocate(); + thread_local sc_core::sc_vpool sc_unsigned_bitref_pool(9); + return sc_unsigned_bitref_pool.allocate(); } sc_unsigned_bitref& operator [] ( int i ) From bc1dfc0db8b89389b0c7a20c4aec8c3bd73c1f04 Mon Sep 17 00:00:00 2001 From: Jerome Haxhiaj Date: Thu, 20 Nov 2025 10:13:37 +0100 Subject: [PATCH 21/50] MSYS2: Remove systemc_if export target This target is redoundant. SystemC::systemc should be used instead Signed-off-by: Jerome Haxhiaj --- src/CMakeLists.txt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f58c796d7..aeb14d5ea 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -489,10 +489,6 @@ endforeach() add_systemc_library(systemc SC_BUILD_OFF STATIC ${SYSTEMC_SC_MAIN_SRC} ) - add_library(systemc_if INTERFACE) - target_link_libraries(systemc_if INTERFACE systemc systemc-${SystemCLanguage_VERSION}) - - if (MSVC) # lib.exe should be available from MSVC command promt add_custom_command( @@ -519,9 +515,8 @@ endforeach() endif(MSVC) - set(SYSTEMC_TARGETS systemc_if systemc ${SYSTEMC_DLL_TARGET}) + set(SYSTEMC_TARGETS systemc ${SYSTEMC_DLL_TARGET}) - add_library(SystemC::systemc ALIAS systemc_if) else(BUILD_SHARED_LIBS AND (WIN32 OR CYGWIN)) add_systemc_library(systemc @@ -533,9 +528,9 @@ else(BUILD_SHARED_LIBS AND (WIN32 OR CYGWIN)) set(SYSTEMC_TARGETS systemc) - add_library(SystemC::systemc ALIAS systemc) endif(BUILD_SHARED_LIBS AND (WIN32 OR CYGWIN)) +add_library(SystemC::systemc ALIAS systemc) install(TARGETS ${SYSTEMC_TARGETS} EXPORT SystemCLanguageTargets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} From e8ae53ec6b9712d857785888d45892e670c7a199 Mon Sep 17 00:00:00 2001 From: Jerome Haxhiaj Date: Tue, 4 Nov 2025 22:07:52 +0100 Subject: [PATCH 22/50] CI: Add msys2 UCRT64 and CLANGARM64 targets This commit adds two new platform for cmake and regressions tests. - MSYS2 clangarm64 (Windows on ARM64 runners) - MSYS2 UCRT64 (Windows Latest runner) Signed-off-by: Jerome Haxhiaj --- .github/workflows/cmake.yml | 38 +++++++++++++++++++++++++++++++ .github/workflows/regressions.yml | 38 +++++++++++++++++++++++++++++++ docker/entrypoint.sh | 36 +++++++++++++++++++++-------- 3 files changed, 102 insertions(+), 10 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 20bf94ea8..25c4454db 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -87,3 +87,41 @@ jobs: cmake -B BUILD/RELEASE/BUILD . cmake --build BUILD/RELEASE/BUILD --config RELEASE --parallel cmake --install BUILD/RELEASE/BUILD --config RELEASE + msys2-ucrt64: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + target: [gcc-shared, gcc-static] + defaults: + run: + shell: msys2 {0} + steps: + - uses: actions/checkout@v5 + - uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: true + install: git mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja + - name: Build + run: | + SYSTEMC_CI_TARGET=${{ matrix.target }} SYSTEMC_SRC_PATH=$PWD docker/entrypoint.sh + msys2-clangarm64: + runs-on: windows-11-arm + strategy: + fail-fast: false + matrix: + target: [clang-shared, clang-static] + defaults: + run: + shell: msys2 {0} + steps: + - uses: actions/checkout@v5 + - uses: msys2/setup-msys2@v2 + with: + msystem: CLANGARM64 + update: true + install: git mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ninja + - name: Build + run: | + SYSTEMC_CI_TARGET=${{ matrix.target }} SYSTEMC_SRC_PATH=$PWD docker/entrypoint.sh diff --git a/.github/workflows/regressions.yml b/.github/workflows/regressions.yml index c2970e0fb..eeb22173f 100644 --- a/.github/workflows/regressions.yml +++ b/.github/workflows/regressions.yml @@ -87,3 +87,41 @@ jobs: cmake -B BUILD/RELEASE/BUILD -DENABLE_REGRESSION=true . cmake --build BUILD/RELEASE/BUILD --config RELEASE --parallel cmake --install BUILD/RELEASE/BUILD --config RELEASE + msys2-ucrt64: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + target: [gcc-shared-regression, gcc-static-regression] + defaults: + run: + shell: msys2 {0} + steps: + - uses: actions/checkout@v5 + - uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + update: true + install: git mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja + - name: Build + run: | + SYSTEMC_CI_TARGET=${{ matrix.target }} SYSTEMC_SRC_PATH=$PWD docker/entrypoint.sh + msys2-clangarm64: + runs-on: windows-11-arm + strategy: + fail-fast: false + matrix: + target: [clang-shared-regression, clang-static-regression] + defaults: + run: + shell: msys2 {0} + steps: + - uses: actions/checkout@v5 + - uses: msys2/setup-msys2@v2 + with: + msystem: CLANGARM64 + update: true + install: git mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ninja + - name: Build + run: | + SYSTEMC_CI_TARGET=${{ matrix.target }} SYSTEMC_SRC_PATH=$PWD docker/entrypoint.sh diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 57207e476..5c83a50ca 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -30,6 +30,12 @@ case "$SYSTEMC_CI_TARGET" in BUILD_SHARED_LIBRARY=true BUILD_REGRESSIONS=true ;; + gcc-static-regression) + CC=gcc + CXX=g++ + BUILD_SHARED_LIBRARY=false + BUILD_REGRESSIONS=true + ;; clang-shared) CC=clang CXX=clang++ @@ -48,6 +54,12 @@ case "$SYSTEMC_CI_TARGET" in BUILD_SHARED_LIBRARY=true BUILD_REGRESSIONS=true ;; + clang-static-regression) + CC=clang + CXX=clang++ + BUILD_SHARED_LIBRARY=false + BUILD_REGRESSIONS=true + ;; clang-shared-regression-asan) CC=clang CXX=clang++ @@ -75,16 +87,20 @@ case "$SYSTEMC_CI_TARGET" in ;; esac -cd $SYSTEMC_SRC_PATH -cmake -B BUILD/RELEASE-${SYSTEMC_CI_TARGET}/BUILD \ - -DCMAKE_INSTALL_PREFIX=${SYSTEMC_SRC_PATH}/BUILD/${SYSTEMC_CI_TARGET} \ + +cd "$SYSTEMC_SRC_PATH" +cmake -B "BUILD/RELEASE-${SYSTEMC_CI_TARGET}/BUILD" \ + -DCMAKE_INSTALL_PREFIX="${SYSTEMC_SRC_PATH}/BUILD/${SYSTEMC_CI_TARGET}" \ -DCMAKE_CXX_FLAGS="$CXX_FLAGS" \ - -DCMAKE_C_COMPILER=$CC \ - -DCMAKE_CXX_COMPILER=$CXX \ - -DENABLE_REGRESSION=$BUILD_REGRESSIONS \ - -DBUILD_SHARED_LIBS=$BUILD_SHARED_LIBRARY . -cmake --build BUILD/RELEASE-${SYSTEMC_CI_TARGET}/BUILD/ --parallel -cmake --install BUILD/RELEASE-${SYSTEMC_CI_TARGET}/BUILD/ -make -j `getconf _NPROCESSORS_ONLN` -C BUILD/RELEASE-${SYSTEMC_CI_TARGET}/BUILD/ check + -DCMAKE_C_COMPILER="$CC" \ + -DCMAKE_CXX_COMPILER="$CXX" \ + -DENABLE_REGRESSION="$BUILD_REGRESSIONS" \ + -DBUILD_SHARED_LIBS="$BUILD_SHARED_LIBRARY" . +cmake --build "BUILD/RELEASE-${SYSTEMC_CI_TARGET}/BUILD/" --parallel +cmake --install "BUILD/RELEASE-${SYSTEMC_CI_TARGET}/BUILD/" + +if [[ "$BUILD_REGRESSIONS" == "true" ]]; then + cmake --build "BUILD/RELEASE-${SYSTEMC_CI_TARGET}/BUILD/" --parallel "$(getconf _NPROCESSORS_ONLN)" --target check +fi exit 0 From b996d15e25f14c26b99a7d61038792f707fd51f0 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Sun, 22 Mar 2026 09:17:24 -0400 Subject: [PATCH 23/50] ACG: Convert FX lists and other statics to thread_local. --- src/sysc/datatypes/fx/sc_fxval.cpp | 2 +- src/sysc/datatypes/fx/scfx_mant.cpp | 2 +- src/sysc/datatypes/fx/scfx_rep.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sysc/datatypes/fx/sc_fxval.cpp b/src/sysc/datatypes/fx/sc_fxval.cpp index a687fe27c..6811858f0 100644 --- a/src/sysc/datatypes/fx/sc_fxval.cpp +++ b/src/sysc/datatypes/fx/sc_fxval.cpp @@ -484,7 +484,7 @@ const char* to_string( const scfx_ieee_double& id, sc_numrep numrep, int w_prefix, sc_fmt fmt, const scfx_params* params = 0 ) { - static scfx_string s; + thread_local scfx_string s; s.clear(); diff --git a/src/sysc/datatypes/fx/scfx_mant.cpp b/src/sysc/datatypes/fx/scfx_mant.cpp index 585c22f7d..346c6eb10 100644 --- a/src/sysc/datatypes/fx/scfx_mant.cpp +++ b/src/sysc/datatypes/fx/scfx_mant.cpp @@ -77,7 +77,7 @@ next_pow2_index( std::size_t size ) return index; } -static word_list* free_words[32] = { 0 }; +thread_local word_list* free_words[32] = { 0 }; word* scfx_mant::alloc_word( std::size_t size ) diff --git a/src/sysc/datatypes/fx/scfx_rep.cpp b/src/sysc/datatypes/fx/scfx_rep.cpp index 385cf091f..66f682505 100644 --- a/src/sysc/datatypes/fx/scfx_rep.cpp +++ b/src/sysc/datatypes/fx/scfx_rep.cpp @@ -81,7 +81,7 @@ namespace sc_dt // some utilities // ---------------------------------------------------------------------------- -static scfx_pow10 pow10_fx; +thread_local scfx_pow10 pow10_fx; static const int mantissa0_size = SCFX_IEEE_DOUBLE_M_SIZE - bits_in_int; @@ -373,7 +373,7 @@ union scfx_rep_node }; -static scfx_rep_node* list = 0; +thread_local scfx_rep_node* list = 0; void* From c7aa9ec0f06059d7450a3a18cbb84627941f6446 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Sat, 4 Apr 2026 21:08:30 -0400 Subject: [PATCH 24/50] Andy Goodrich: fix right shift operators to IEEE standard Accoding to IEEE 2023 the width of rsults from of right shifts should be the same ss the input variable. --- src/sysc/datatypes/int/sc_biguint_inlines.h | 15 ++++++++------- src/sysc/datatypes/int/sc_unsigned.h | 6 ++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/sysc/datatypes/int/sc_biguint_inlines.h b/src/sysc/datatypes/int/sc_biguint_inlines.h index 151e5a855..0954a090e 100644 --- a/src/sysc/datatypes/int/sc_biguint_inlines.h +++ b/src/sysc/datatypes/int/sc_biguint_inlines.h @@ -468,7 +468,7 @@ sc_biguint::operator>>(int v) const // If we shift off the end return a single bit 0. if ( nb <= 0 ) { - sc_unsigned result(1, true); + sc_unsigned result(W, true); return result; } @@ -478,24 +478,25 @@ sc_biguint::operator>>(int v) const // Note: sc_biguint values have one extra bit on the top, so the comparison is down // one bit, e.g., W < 32, not W < 33. - sc_unsigned result(nb, false); if ( W < 32 ) { + sc_unsigned result(W, false); result.digit[0] = digit[0] >> v; + return result; } else if ( W < 64 ) { + sc_unsigned result(W, false); uint64 tmp = digit[DIV_CEIL(W)-1]; tmp = (tmp << 32) | digit[0]; tmp = tmp >> v; result.digit[0] = tmp; - if ( result.nbits > 32 ) { - result.digit[1] = (tmp >> 32); - } + result.digit[1] = (tmp >> 32); + return result; } else { + sc_unsigned result(W, true); vector_extract(digit, result.digit, W-1, v); + return result; } - result.adjust_hod(); - return result; } template diff --git a/src/sysc/datatypes/int/sc_unsigned.h b/src/sysc/datatypes/int/sc_unsigned.h index 4a5ed2c49..e323d2551 100644 --- a/src/sysc/datatypes/int/sc_unsigned.h +++ b/src/sysc/datatypes/int/sc_unsigned.h @@ -1081,9 +1081,12 @@ class SC_API sc_unsigned : public sc_value_base sc_unsigned result(nb, false); if ( nbits < 33 ) { + sc_unsigned result(nbits, false); result.digit[0] = (int)digit[0] >> v; + return result; } else if ( nbits < 65 ) { + sc_unsigned result(nbits, false); int64 tmp = digit[1]; tmp = (tmp << 32) | digit[0]; tmp = tmp >> v; @@ -1091,9 +1094,12 @@ class SC_API sc_unsigned : public sc_value_base if ( nb > 32 ) { result.digit[1] = (tmp >>32); } + return result; } else { + sc_unsigned result(nbits, true); vector_extract(digit, result.digit, nbits-1, v); + return result; } result.adjust_hod(); return result; From fc06769792c790520ab0b357e2819a9e61413d89 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Sat, 4 Apr 2026 22:50:54 -0400 Subject: [PATCH 25/50] Andy Goodrich: further changes to return same sized values after right shifts. --- src/sysc/datatypes/int/sc_bigint_inlines.h | 22 +++++++++++---------- src/sysc/datatypes/int/sc_biguint_inlines.h | 12 +++++------ src/sysc/datatypes/int/sc_signed.h | 20 ++++++++++--------- src/sysc/datatypes/int/sc_unsigned.h | 9 ++------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/sysc/datatypes/int/sc_bigint_inlines.h b/src/sysc/datatypes/int/sc_bigint_inlines.h index 97d35fd82..fd064237b 100644 --- a/src/sysc/datatypes/int/sc_bigint_inlines.h +++ b/src/sysc/datatypes/int/sc_bigint_inlines.h @@ -475,7 +475,7 @@ sc_bigint::operator>>=(int v) vector_shift_right(nd, digit, v, (int)digit[nd-1]<0 ? DIGIT_MASK:0); - return *this; + return *this; } // right shift methods: @@ -492,31 +492,33 @@ sc_bigint::operator>>(int v) const // If we shift off the end return the sign bit. if ( 0 >= nb ) { - sc_signed result(1, false); - result.digit[0] = 0 > (int)digit[HOD] ? -1 : 0; + sc_signed result(W, false); + result = (int)digit[HOD] ? -1 : 0; return result; } // Return a value that is the width of the shifted value: - sc_signed result(nb, false); if ( W < 33 ) { + sc_signed result(W, false); result.digit[0] = (int)digit[0] >> v; + return result; } else if ( W < 65 ) { + sc_signed result(W, false); int64 tmp = digit[DIV_CEIL(W)-1]; tmp = (tmp << 32) | digit[0]; tmp = tmp >> v; result.digit[0] = tmp; - if ( nb > 32 ) { - result.digit[1] = (tmp >>32); - } + result.digit[1] = (tmp >>32); + return result; } else { - vector_extract(digit, result.digit, W-1, v); + int nd = DIV_CEIL(W); + sc_signed result(*this); + vector_shift_right(nd, result.digit, v, (int)result.digit[nd-1]<0 ? DIGIT_MASK:0); + return result; } - result.adjust_hod(); - return result; } // sc_bv and sc_lv constructors and assignments using an sc_bigint value: diff --git a/src/sysc/datatypes/int/sc_biguint_inlines.h b/src/sysc/datatypes/int/sc_biguint_inlines.h index 0954a090e..9de809c2f 100644 --- a/src/sysc/datatypes/int/sc_biguint_inlines.h +++ b/src/sysc/datatypes/int/sc_biguint_inlines.h @@ -468,7 +468,7 @@ sc_biguint::operator>>(int v) const // If we shift off the end return a single bit 0. if ( nb <= 0 ) { - sc_unsigned result(W, true); + sc_unsigned result(nbits, true); return result; } @@ -479,13 +479,13 @@ sc_biguint::operator>>(int v) const // one bit, e.g., W < 32, not W < 33. if ( W < 32 ) { - sc_unsigned result(W, false); + sc_unsigned result(nbits, false); result.digit[0] = digit[0] >> v; return result; } else if ( W < 64 ) { - sc_unsigned result(W, false); - uint64 tmp = digit[DIV_CEIL(W)-1]; + sc_unsigned result(nbits, false); + uint64 tmp = digit[1]; tmp = (tmp << 32) | digit[0]; tmp = tmp >> v; result.digit[0] = tmp; @@ -493,8 +493,8 @@ sc_biguint::operator>>(int v) const return result; } else { - sc_unsigned result(W, true); - vector_extract(digit, result.digit, W-1, v); + sc_unsigned result(nbits, true); + vector_extract(digit, result.digit, nbits-1, v); return result; } } diff --git a/src/sysc/datatypes/int/sc_signed.h b/src/sysc/datatypes/int/sc_signed.h index 091465203..285e07712 100644 --- a/src/sysc/datatypes/int/sc_signed.h +++ b/src/sysc/datatypes/int/sc_signed.h @@ -1080,31 +1080,33 @@ class SC_API sc_signed : public sc_value_base // If we shift off the end return the sign bit. if ( 0 >= nb ) { - sc_signed result(1, false); - result.digit[0] = 0 > (int)digit[ndigits-1] ? -1 : 0; + sc_signed result(nbits, false); + result = 0 > (int)digit[ndigits-1] ? -1 : 0; return result; } // Return a value that is the width of the shifted value: - sc_signed result(nb, false); if ( nbits < 33 ) { + sc_signed result(nbits, false); result.digit[0] = (int)digit[0] >> v; + return result; } else if ( nbits < 65 ) { + sc_signed result(nbits, false); int64 tmp = digit[1]; tmp = (tmp << 32) | digit[0]; tmp = tmp >> v; result.digit[0] = (sc_digit)tmp; - if ( nb > 32 ) { - result.digit[1] = (tmp >>32); - } + result.digit[1] = (tmp >>32); + return result; } else { - vector_extract(digit, result.digit, nbits-1, v); + int nd = DIV_CEIL(nbits); + sc_signed result(*this); + vector_shift_right(nd, result.digit, v, (int)result.digit[nd-1]<0 ? DIGIT_MASK:0); + return result; } - result.adjust_hod(); - return result; } sc_signed operator>>(const sc_unsigned& v) const; diff --git a/src/sysc/datatypes/int/sc_unsigned.h b/src/sysc/datatypes/int/sc_unsigned.h index e323d2551..f90906faa 100644 --- a/src/sysc/datatypes/int/sc_unsigned.h +++ b/src/sysc/datatypes/int/sc_unsigned.h @@ -1073,13 +1073,12 @@ class SC_API sc_unsigned : public sc_value_base // If we shift off the end return the sign bit. if ( 0 >= nb ) { - sc_unsigned result(1, true); + sc_unsigned result(nbits, true); return result; } // Return a value that is the width of the shifted value: - sc_unsigned result(nb, false); if ( nbits < 33 ) { sc_unsigned result(nbits, false); result.digit[0] = (int)digit[0] >> v; @@ -1091,9 +1090,7 @@ class SC_API sc_unsigned : public sc_value_base tmp = (tmp << 32) | digit[0]; tmp = tmp >> v; result.digit[0] = (sc_digit)tmp; - if ( nb > 32 ) { - result.digit[1] = (tmp >>32); - } + result.digit[1] = (tmp>>32); return result; } else { @@ -1101,8 +1098,6 @@ class SC_API sc_unsigned : public sc_value_base vector_extract(digit, result.digit, nbits-1, v); return result; } - result.adjust_hod(); - return result; } sc_unsigned operator>>(const sc_signed& v) const; From 1071551e78a3da44e685a50a7cad41f0bc31f496 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Thu, 2 Apr 2026 09:42:10 -0400 Subject: [PATCH 26/50] Andy Goodrich: change static to thread_local for scfx_rep::to_string. --- src/sysc/datatypes/fx/scfx_rep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sysc/datatypes/fx/scfx_rep.cpp b/src/sysc/datatypes/fx/scfx_rep.cpp index 66f682505..64307bdbe 100644 --- a/src/sysc/datatypes/fx/scfx_rep.cpp +++ b/src/sysc/datatypes/fx/scfx_rep.cpp @@ -1272,7 +1272,7 @@ const char* scfx_rep::to_string( sc_numrep numrep, int w_prefix, sc_fmt fmt, const scfx_params* params ) const { - static scfx_string s; + thread_local scfx_string s; s.clear(); From 4c1a42f258e0a1f240c4e3247cd90b3786ddc453 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Tue, 14 Apr 2026 07:50:32 -0400 Subject: [PATCH 27/50] Andy Goodrich: alignment issue. --- src/sysc/datatypes/int/sc_unsigned.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sysc/datatypes/int/sc_unsigned.h b/src/sysc/datatypes/int/sc_unsigned.h index f90906faa..14021f1c8 100644 --- a/src/sysc/datatypes/int/sc_unsigned.h +++ b/src/sysc/datatypes/int/sc_unsigned.h @@ -1114,7 +1114,7 @@ class SC_API sc_unsigned : public sc_value_base if (v <= 0) return *this; vector_shift_right(ndigits, digit, v, 0); - return *this; + return *this; } const sc_unsigned& operator>>=(const sc_signed& v); const sc_unsigned& operator>>=(const sc_unsigned& v) { return operator>>=(v.to_int()); } From c5153d64ce62e5c17d19b0944a87de2fbdebd90d Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Tue, 14 Apr 2026 07:56:17 -0400 Subject: [PATCH 28/50] Andy Goodrich: convert indents to spaces rather than tabs. --- src/sysc/datatypes/int/sc_biguint_inlines.h | 25 ++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/sysc/datatypes/int/sc_biguint_inlines.h b/src/sysc/datatypes/int/sc_biguint_inlines.h index 9de809c2f..52a39be61 100644 --- a/src/sysc/datatypes/int/sc_biguint_inlines.h +++ b/src/sysc/datatypes/int/sc_biguint_inlines.h @@ -479,23 +479,23 @@ sc_biguint::operator>>(int v) const // one bit, e.g., W < 32, not W < 33. if ( W < 32 ) { - sc_unsigned result(nbits, false); - result.digit[0] = digit[0] >> v; - return result; + sc_unsigned result(nbits, false); + result.digit[0] = digit[0] >> v; + return result; } else if ( W < 64 ) { - sc_unsigned result(nbits, false); + sc_unsigned result(nbits, false); uint64 tmp = digit[1]; tmp = (tmp << 32) | digit[0]; - tmp = tmp >> v; - result.digit[0] = tmp; - result.digit[1] = (tmp >> 32); - return result; + tmp = tmp >> v; + result.digit[0] = tmp; + result.digit[1] = (tmp >> 32); + return result; } else { - sc_unsigned result(nbits, true); - vector_extract(digit, result.digit, nbits-1, v); - return result; + sc_unsigned result(nbits, true); + vector_extract(digit, result.digit, nbits-1, v); + return result; } } @@ -503,14 +503,13 @@ template const sc_biguint& sc_biguint::operator>>=(int v) { - if (v <= 0) return *this; int nd = DIV_CEIL(W+1); vector_shift_right(nd, digit, v, 0); - return *this; + return *this; } // sc_bv and sc_lv constructors and assignments using an sc_biguint value: From e5bf3e82dfaacdb2ef56033b7ad8163209bf7b02 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Tue, 14 Apr 2026 07:36:52 -0400 Subject: [PATCH 29/50] Andy Goodrich: add adjust_hod calls to set() and clear() --- src/sysc/datatypes/int/sc_signed.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sysc/datatypes/int/sc_signed.h b/src/sysc/datatypes/int/sc_signed.h index 285e07712..98702f3e4 100644 --- a/src/sysc/datatypes/int/sc_signed.h +++ b/src/sysc/datatypes/int/sc_signed.h @@ -839,6 +839,7 @@ class SC_API sc_signed : public sc_value_base digit[digit_num] |= one_and_zeros(bit_num); digit[digit_num] = SC_MASK_DIGIT(digit[digit_num]); + adjust_hod(); } @@ -855,6 +856,7 @@ class SC_API sc_signed : public sc_value_base digit[digit_num] &= ~(one_and_zeros(bit_num)); digit[digit_num] = SC_MASK_DIGIT(digit[digit_num]); + adjust_hod(); } From ee3599a3ba19a29bc40336c9430444fac5a9ee34 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Thu, 16 Apr 2026 05:01:14 -0400 Subject: [PATCH 30/50] Andy Goodrich: fix indentation. --- src/sysc/datatypes/int/sc_bigint_inlines.h | 28 ++++++++++----------- src/sysc/datatypes/int/sc_biguint_inlines.h | 2 +- src/sysc/datatypes/int/sc_signed.h | 8 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/sysc/datatypes/int/sc_bigint_inlines.h b/src/sysc/datatypes/int/sc_bigint_inlines.h index fd064237b..ecb2be324 100644 --- a/src/sysc/datatypes/int/sc_bigint_inlines.h +++ b/src/sysc/datatypes/int/sc_bigint_inlines.h @@ -492,32 +492,32 @@ sc_bigint::operator>>(int v) const // If we shift off the end return the sign bit. if ( 0 >= nb ) { - sc_signed result(W, false); + sc_signed result(W, false); result = (int)digit[HOD] ? -1 : 0; - return result; + return result; } // Return a value that is the width of the shifted value: if ( W < 33 ) { - sc_signed result(W, false); - result.digit[0] = (int)digit[0] >> v; - return result; + sc_signed result(W, false); + result.digit[0] = (int)digit[0] >> v; + return result; } else if ( W < 65 ) { - sc_signed result(W, false); + sc_signed result(W, false); int64 tmp = digit[DIV_CEIL(W)-1]; tmp = (tmp << 32) | digit[0]; - tmp = tmp >> v; - result.digit[0] = tmp; - result.digit[1] = (tmp >>32); - return result; + tmp = tmp >> v; + result.digit[0] = tmp; + result.digit[1] = (tmp >>32); + return result; } else { - int nd = DIV_CEIL(W); - sc_signed result(*this); - vector_shift_right(nd, result.digit, v, (int)result.digit[nd-1]<0 ? DIGIT_MASK:0); - return result; + int nd = DIV_CEIL(W); + sc_signed result(*this); + vector_shift_right(nd, result.digit, v, (int)result.digit[nd-1]<0 ? DIGIT_MASK:0); + return result; } } diff --git a/src/sysc/datatypes/int/sc_biguint_inlines.h b/src/sysc/datatypes/int/sc_biguint_inlines.h index 52a39be61..757b16afb 100644 --- a/src/sysc/datatypes/int/sc_biguint_inlines.h +++ b/src/sysc/datatypes/int/sc_biguint_inlines.h @@ -469,7 +469,7 @@ sc_biguint::operator>>(int v) const if ( nb <= 0 ) { sc_unsigned result(nbits, true); - return result; + return result; } diff --git a/src/sysc/datatypes/int/sc_signed.h b/src/sysc/datatypes/int/sc_signed.h index 98702f3e4..23c1a83d6 100644 --- a/src/sysc/datatypes/int/sc_signed.h +++ b/src/sysc/datatypes/int/sc_signed.h @@ -1104,10 +1104,10 @@ class SC_API sc_signed : public sc_value_base return result; } else { - int nd = DIV_CEIL(nbits); - sc_signed result(*this); - vector_shift_right(nd, result.digit, v, (int)result.digit[nd-1]<0 ? DIGIT_MASK:0); - return result; + int nd = DIV_CEIL(nbits); + sc_signed result(*this); + vector_shift_right(nd, result.digit, v, (int)result.digit[nd-1]<0 ? DIGIT_MASK:0); + return result; } } From f29b92dec240b70ff2cc07a200127be4f927f4a3 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Thu, 16 Apr 2026 05:06:16 -0400 Subject: [PATCH 31/50] Andy Goodrich: more indentation fixes --- src/sysc/datatypes/int/sc_unsigned.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sysc/datatypes/int/sc_unsigned.h b/src/sysc/datatypes/int/sc_unsigned.h index 14021f1c8..fe6af20eb 100644 --- a/src/sysc/datatypes/int/sc_unsigned.h +++ b/src/sysc/datatypes/int/sc_unsigned.h @@ -1080,23 +1080,23 @@ class SC_API sc_unsigned : public sc_value_base // Return a value that is the width of the shifted value: if ( nbits < 33 ) { - sc_unsigned result(nbits, false); + sc_unsigned result(nbits, false); result.digit[0] = (int)digit[0] >> v; - return result; + return result; } else if ( nbits < 65 ) { - sc_unsigned result(nbits, false); + sc_unsigned result(nbits, false); int64 tmp = digit[1]; tmp = (tmp << 32) | digit[0]; tmp = tmp >> v; result.digit[0] = (sc_digit)tmp; - result.digit[1] = (tmp>>32); - return result; + result.digit[1] = (tmp>>32); + return result; } else { - sc_unsigned result(nbits, true); + sc_unsigned result(nbits, true); vector_extract(digit, result.digit, nbits-1, v); - return result; + return result; } } From 6bee7f5b34ca9f685ba1d69fc4020ba299ff35ed Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Thu, 16 Apr 2026 05:29:49 -0400 Subject: [PATCH 32/50] Andy Goodrich: match standard for unsigned ~ operator. --- src/sysc/datatypes/int/sc_biguint.h | 2 +- src/sysc/datatypes/int/sc_biguint_inlines.h | 5 +++-- src/sysc/datatypes/int/sc_signed_ops.h | 8 +++----- src/sysc/datatypes/int/sc_unsigned.h | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/sysc/datatypes/int/sc_biguint.h b/src/sysc/datatypes/int/sc_biguint.h index 3cbb19e10..584ac0a03 100644 --- a/src/sysc/datatypes/int/sc_biguint.h +++ b/src/sysc/datatypes/int/sc_biguint.h @@ -319,7 +319,7 @@ class sc_biguint // unary operators: inline const sc_bigint operator - (); - inline const sc_bigint operator ~ (); + inline const sc_biguint operator ~ (); // assignment operators diff --git a/src/sysc/datatypes/int/sc_biguint_inlines.h b/src/sysc/datatypes/int/sc_biguint_inlines.h index 757b16afb..f31ecf47e 100644 --- a/src/sysc/datatypes/int/sc_biguint_inlines.h +++ b/src/sysc/datatypes/int/sc_biguint_inlines.h @@ -226,13 +226,14 @@ sc_biguint::operator - () } template -inline const sc_bigint +inline const sc_biguint sc_biguint::operator ~ () { - sc_bigint result; + sc_biguint result; for ( int digit_i = 0; digit_i <= HOD; ++digit_i ) { result.digit[digit_i] = ~digit[digit_i]; } + result.adjust_hod(); return result; } diff --git a/src/sysc/datatypes/int/sc_signed_ops.h b/src/sysc/datatypes/int/sc_signed_ops.h index faa40e8f0..b4bd0780b 100644 --- a/src/sysc/datatypes/int/sc_signed_ops.h +++ b/src/sysc/datatypes/int/sc_signed_ops.h @@ -1531,19 +1531,17 @@ operator~(const sc_signed& u) } inline -sc_signed +sc_unsigned operator~(const sc_unsigned& u) { - sc_signed result( u.length()+1 ); + sc_unsigned result( u.length() ); sc_digit* result_p = result.get_digits(); sc_digit* source_p = u.get_digits(); int hod = u.get_hod(); for ( int digit_i = 0; digit_i <= hod; ++digit_i ) { result_p[digit_i] = ~source_p[digit_i]; } - if ( result.get_hod() > hod ) { - result_p[hod] = (sc_digit)-1; - } + result.adjust_hod(); return result; } diff --git a/src/sysc/datatypes/int/sc_unsigned.h b/src/sysc/datatypes/int/sc_unsigned.h index fe6af20eb..f1e0b8039 100644 --- a/src/sysc/datatypes/int/sc_unsigned.h +++ b/src/sysc/datatypes/int/sc_unsigned.h @@ -138,7 +138,7 @@ class sc_fxnum_fast; // Bitwise NOT operator (unary). - SC_API sc_signed operator ~ (const sc_unsigned& u); + SC_API sc_unsigned operator ~ (const sc_unsigned& u); // ---------------------------------------------------------------------------- // CLASS : sc_unsigned_bitref_r @@ -1131,7 +1131,7 @@ class SC_API sc_unsigned : public sc_value_base // Bitwise NOT operator (unary). - friend SC_API sc_signed operator ~ (const sc_unsigned& u); + friend SC_API sc_unsigned operator ~ (const sc_unsigned& u); protected: From 64b86eec0048d494fe72191e06a90c4654345c96 Mon Sep 17 00:00:00 2001 From: Andy Goodrich Date: Thu, 16 Apr 2026 05:54:31 -0400 Subject: [PATCH 33/50] Andy Goodrich: adjust test for new result sign for operator~. --- .../datatypes/int/big_datatypes/add_subtracts/add_subtracts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/systemc/datatypes/int/big_datatypes/add_subtracts/add_subtracts.cpp b/tests/systemc/datatypes/int/big_datatypes/add_subtracts/add_subtracts.cpp index e10bdd2bf..1e4027b0c 100644 --- a/tests/systemc/datatypes/int/big_datatypes/add_subtracts/add_subtracts.cpp +++ b/tests/systemc/datatypes/int/big_datatypes/add_subtracts/add_subtracts.cpp @@ -294,7 +294,7 @@ class AddSubtract : public AddSubtract assert( v_difference == v_sc_biguint_a ); } - if ( -v_sc_biguint_a != ( ~v_sc_biguint_a + 1 ) ) { + if ( -v_sc_biguint_a != ( ~(sc_signed)v_sc_biguint_a + 1 ) ) { cout << "ERROR: -a != ~a+1 in " << __FILE__ << " at line " << __LINE__ << endl; cout << " W " << W << endl; cout << " -v_sc_biguint_a " << -v_sc_biguint_a << endl; From 318a56babbac1d8c6e00a02f64ef4f3e9e5b3764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20J=C3=BCnger?= Date: Wed, 22 Apr 2026 15:34:46 +0200 Subject: [PATCH 34/50] CI: Don't build with -Werror on AlmaLinux 8 on arm64 hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC8.5 does not ignore the false positive -Wshift-negative-value. Pragmas introduced in 7ff1521bf7a49f8897809f108ecf273a1922d6fb do not work. Signed-off-by: Lukas Jünger --- docker/entrypoint.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 5c83a50ca..4fd4c1b61 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -11,6 +11,19 @@ fi # Build with -Werror by default CXX_FLAGS="-Werror" +# Don't build with -Werror on AlmaLinux 8 on arm64 hosts +# GCC 8.5 does not ignore false positive -Wshift-negative-value warning +if [[ -f /etc/os-release ]]; then + . /etc/os-release + + ARCH=$(uname -m) + + if [[ "$ID" == "almalinux" && "$VERSION_ID" == 8* ]] && \ + [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then + CXX_FLAGS="" + fi +fi + case "$SYSTEMC_CI_TARGET" in gcc-shared) CC=gcc From 53e5f7d631c423212cea6e62e0affd8592497d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20J=C3=BCnger?= Date: Wed, 22 Apr 2026 16:03:06 +0200 Subject: [PATCH 35/50] ci: remove Ubuntu 20.04, because it is end of life MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lukas Jünger --- .github/workflows/asan.yml | 4 ++-- .github/workflows/cmake.yml | 4 ++-- .github/workflows/regressions.yml | 4 ++-- .github/workflows/tsan.yml | 4 ++-- .github/workflows/ubsan.yml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/asan.yml b/.github/workflows/asan.yml index ea44d0cbe..263072b67 100644 --- a/.github/workflows/asan.yml +++ b/.github/workflows/asan.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/amd64] target: [clang-shared-regression-asan] steps: @@ -40,7 +40,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/arm64] target: [clang-shared-regression-asan] steps: diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 25c4454db..b20ab87a2 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/amd64] target: [gcc-shared, gcc-static, clang-shared, clang-static] steps: @@ -40,7 +40,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/arm64] target: [gcc-shared, gcc-static, clang-shared, clang-static] steps: diff --git a/.github/workflows/regressions.yml b/.github/workflows/regressions.yml index eeb22173f..2a3a864f9 100644 --- a/.github/workflows/regressions.yml +++ b/.github/workflows/regressions.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/amd64] target: [gcc-shared-regression, clang-shared-regression] steps: @@ -40,7 +40,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/arm64] target: [gcc-shared-regression, clang-shared-regression] steps: diff --git a/.github/workflows/tsan.yml b/.github/workflows/tsan.yml index 2a058c547..cd67a02b4 100644 --- a/.github/workflows/tsan.yml +++ b/.github/workflows/tsan.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/amd64] target: [clang-shared-regression-tsan] steps: @@ -40,7 +40,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/arm64] target: [clang-shared-regression-tsan] steps: diff --git a/.github/workflows/ubsan.yml b/.github/workflows/ubsan.yml index bb983a642..5a145e7ee 100644 --- a/.github/workflows/ubsan.yml +++ b/.github/workflows/ubsan.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/amd64] target: [clang-shared-regression-ubsan] steps: @@ -40,7 +40,7 @@ jobs: strategy: fail-fast: false matrix: - version: [20.04, 22.04, 24.04] + version: [22.04, 24.04] platform: [linux/arm64] target: [clang-shared-regression-ubsan] steps: From 93452838572d10909543d7ba03dc41d07e51cc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20J=C3=BCnger?= Date: Wed, 22 Apr 2026 16:16:18 +0200 Subject: [PATCH 36/50] tests: fix unused return value for regression tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lukas Jünger --- tests/systemc/misc/sim_tests/simple_cpu/simple_cpu.cpp | 8 +++++--- tests/systemc/misc/user_guide/chpt3.1/sg.cpp | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/systemc/misc/sim_tests/simple_cpu/simple_cpu.cpp b/tests/systemc/misc/sim_tests/simple_cpu/simple_cpu.cpp index 3d941a67f..86a538688 100644 --- a/tests/systemc/misc/sim_tests/simple_cpu/simple_cpu.cpp +++ b/tests/systemc/misc/sim_tests/simple_cpu/simple_cpu.cpp @@ -66,10 +66,11 @@ SC_MODULE( exec_decode ) // Initialize the data memory from file datamem FILE *fp = fopen("simple_cpu/datamem", "r"); - if (fp == (FILE *) 0) return; // No data mem file to read + if (fp == (FILE *) 0) assert(); // No data mem file to read // First field in this file is the size of data memory desired int size; - fscanf(fp, "%d", &size); + int ret = fscanf(fp, "%d", &size); + (void) ret; data_mem = new unsigned[size]; if (data_mem == (unsigned *) 0) { printf("Not enough memory left\n"); @@ -207,7 +208,8 @@ SC_MODULE( fetch ) if (fp == (FILE *) 0) return; // No prog mem file to read // First field in this file is the size of program memory desired int size; - fscanf(fp, "%d", &size); + int ret = fscanf(fp, "%d", &size); + (void) ret; prog_mem = new unsigned[size]; if (prog_mem == (unsigned *) 0) { printf("Not enough memory left\n"); diff --git a/tests/systemc/misc/user_guide/chpt3.1/sg.cpp b/tests/systemc/misc/user_guide/chpt3.1/sg.cpp index 65edd73e6..b2bdb6aae 100644 --- a/tests/systemc/misc/user_guide/chpt3.1/sg.cpp +++ b/tests/systemc/misc/user_guide/chpt3.1/sg.cpp @@ -47,7 +47,8 @@ void stimgen::entry() file = fopen("./chpt3.1/testcase", "r"); while (true) { - fscanf(file, "%c", &c); + int ret = fscanf(file, "%c", &c); + (void) ret; data_ready.write(true); stream.write(c); wait(); From adddd511ea1f19900e4e7328ec1371c40b10f768 Mon Sep 17 00:00:00 2001 From: Nils Bosbach Date: Wed, 22 Apr 2026 18:12:27 +0200 Subject: [PATCH 37/50] tests: fix memory leaks Signed-off-by: Nils Bosbach --- examples/sysc/async_suspend/node.h | 14 +- examples/sysc/risc_cpu/bios.h | 5 + examples/sysc/risc_cpu/dcache.h | 6 + examples/sysc/risc_cpu/icache.h | 5 + examples/sysc/simple_bus/simple_bus_main.cpp | 2 + tests/include/tlm/CoreDecouplingLTInitiator.h | 32 ++-- tests/include/tlm/SimpleLTInitiator1.h | 19 +- tests/include/tlm/SimpleLTInitiator1_DMI.h | 134 ++++++------- tests/include/tlm/SimpleLTInitiator2.h | 21 +- tests/include/tlm/SimpleLTInitiator2_DMI.h | 134 ++++++------- tests/include/tlm/SimpleLTInitiator3.h | 22 ++- tests/include/tlm/SimpleLTInitiator3_DMI.h | 140 +++++++------- .../child_proc_control/child_proc_control.cpp | 11 ++ .../event_list/event_list.cpp | 26 +-- .../method_with_reset/method_with_reset.cpp | 4 +- .../mixed_child_procs/mixed_child_procs.cpp | 11 ++ .../sc_vector/sc_vector.cpp | 3 +- .../compliance_1666/test001/test001.cpp | 18 ++ .../compliance_1666/test234/test234.cpp | 22 ++- .../dynamic_processes/test06/test06.cpp | 6 +- .../dynamic_processes/test07/test07.cpp | 14 +- .../kernel/process_control/test06/test06.cpp | 43 +++-- .../sc_event/test15/event_triggered.cpp | 4 +- .../misc/ieee1666_2023/5.7.3/5.7.3.cpp | 7 +- .../sim_tests/biquad/biquad2/delay_line.h | 4 + .../misc/sim_tests/biquad/biquad2/op_queue.h | 4 + .../sim_tests/biquad/biquad3/delay_line.h | 4 + .../misc/sim_tests/simple_cpu/simple_cpu.cpp | 8 + tests/tlm/cancel_all/cancel_all.cpp | 37 +++- tests/tlm/nb2b_adapter/mm.h | 2 + tests/tlm/nb2b_adapter/nb2b_adapter.cpp | 37 ++-- .../ext2gp/SimpleLTInitiator_ext.h | 180 +++++++++--------- .../ext2gp2ext/SimpleLTInitiator_ext.h | 161 ++++++++-------- 33 files changed, 649 insertions(+), 491 deletions(-) diff --git a/examples/sysc/async_suspend/node.h b/examples/sysc/async_suspend/node.h index 419c882eb..34cb275e2 100644 --- a/examples/sysc/async_suspend/node.h +++ b/examples/sysc/async_suspend/node.h @@ -118,6 +118,7 @@ SC_MODULE (asynctestnode) init_socket("output"), target_socket("input"), txnSent_c(0), + txn(nullptr), suspendReq(false), col(c), running(true), @@ -136,12 +137,22 @@ SC_MODULE (asynctestnode) sensitive << txnSentEvent; } - ~asynctestnode() + virtual ~asynctestnode() { running = false; while (!finished) txnSentMethod(); if (m_thread.joinable()) m_thread.join(); + + delete txn; + txn = nullptr; + + while (!queue.empty()) + { + delete queue.front(); + queue.pop(); + } + } // This will cause SystemC time to be driven forwards. But, if we're not @@ -239,6 +250,7 @@ SC_MODULE (asynctestnode) // Send transaction to a random place init_socket[rand() % init_socket.size()]->b_transport(*txn, myTime); + txn = nullptr; #ifdef DEBUG std::stringstream msg; diff --git a/examples/sysc/risc_cpu/bios.h b/examples/sysc/risc_cpu/bios.h index 19a0bb056..f0fd6ca8e 100644 --- a/examples/sysc/risc_cpu/bios.h +++ b/examples/sysc/risc_cpu/bios.h @@ -82,6 +82,11 @@ struct bios : sc_module { } } + virtual ~bios() { + delete [] imemory; + delete [] itagmemory; + } + // Process functionality in member function below void entry(); }; diff --git a/examples/sysc/risc_cpu/dcache.h b/examples/sysc/risc_cpu/dcache.h index 93d56fe94..a71e79458 100644 --- a/examples/sysc/risc_cpu/dcache.h +++ b/examples/sysc/risc_cpu/dcache.h @@ -86,6 +86,12 @@ struct dcache : sc_module { } } + virtual ~dcache() { + delete [] dmemory; + delete [] dsmemory; + delete [] dtagmemory; + } + // Process functionality in member function below void entry(); }; diff --git a/examples/sysc/risc_cpu/icache.h b/examples/sysc/risc_cpu/icache.h index 44e7ced9e..57a327140 100644 --- a/examples/sysc/risc_cpu/icache.h +++ b/examples/sysc/risc_cpu/icache.h @@ -85,6 +85,11 @@ struct icache : sc_module { } } + virtual ~icache() { + delete [] icmemory; + delete [] ictagmemory; + } + // Process functionality in member function below void entry(); }; diff --git a/examples/sysc/simple_bus/simple_bus_main.cpp b/examples/sysc/simple_bus/simple_bus_main.cpp index 05e4bac2e..8fa7d527b 100644 --- a/examples/sysc/simple_bus/simple_bus_main.cpp +++ b/examples/sysc/simple_bus/simple_bus_main.cpp @@ -44,5 +44,7 @@ int sc_main(int, char **) sc_start(10000, SC_NS); + fflush(stdout); + return 0; } diff --git a/tests/include/tlm/CoreDecouplingLTInitiator.h b/tests/include/tlm/CoreDecouplingLTInitiator.h index 2a1965c1f..1090a4c4d 100644 --- a/tests/include/tlm/CoreDecouplingLTInitiator.h +++ b/tests/include/tlm/CoreDecouplingLTInitiator.h @@ -132,22 +132,24 @@ class CoreDecouplingLTInitiator : public sc_core::sc_module void run() { - transaction_type trans; - - while (initTransaction(trans)) { - logStartTransation(trans); - - // exec instr - sc_core::sc_time t = mQuantumKeeper.get_local_time(); - socket->b_transport(trans, t); - mQuantumKeeper.set(t); - // Target may have added a delay to the quantum -> sync if needed - if (mQuantumKeeper.need_sync()) { - std::cout << "Sync'ing..." << std::endl; - mQuantumKeeper.sync(); + { + transaction_type trans; + + while (initTransaction(trans)) { + logStartTransation(trans); + + // exec instr + sc_core::sc_time t = mQuantumKeeper.get_local_time(); + socket->b_transport(trans, t); + mQuantumKeeper.set(t); + // Target may have added a delay to the quantum -> sync if needed + if (mQuantumKeeper.need_sync()) { + std::cout << "Sync'ing..." << std::endl; + mQuantumKeeper.sync(); + } + + logEndTransaction(trans); } - - logEndTransaction(trans); } wait(); } diff --git a/tests/include/tlm/SimpleLTInitiator1.h b/tests/include/tlm/SimpleLTInitiator1.h index 38660f7ee..e8b753d6b 100644 --- a/tests/include/tlm/SimpleLTInitiator1.h +++ b/tests/include/tlm/SimpleLTInitiator1.h @@ -124,14 +124,17 @@ class SimpleLTInitiator1 : void run() { - transaction_type trans; - sc_core::sc_time t(sc_core::SC_ZERO_TIME); - while (initTransaction(trans)) { - logStartTransation(trans); - socket->b_transport(trans, t); - wait(t); - logEndTransaction(trans); - t = sc_core::SC_ZERO_TIME; + // scope needed to free the memory of the local variables (co-routine stacks are not proper cleaned up at simulation end) + { + transaction_type trans; + sc_core::sc_time t(sc_core::SC_ZERO_TIME); + while (initTransaction(trans)) { + logStartTransation(trans); + socket->b_transport(trans, t); + wait(t); + logEndTransaction(trans); + t = sc_core::SC_ZERO_TIME; + } } wait(); diff --git a/tests/include/tlm/SimpleLTInitiator1_DMI.h b/tests/include/tlm/SimpleLTInitiator1_DMI.h index 00ec7c582..898feeb05 100644 --- a/tests/include/tlm/SimpleLTInitiator1_DMI.h +++ b/tests/include/tlm/SimpleLTInitiator1_DMI.h @@ -131,73 +131,75 @@ class SimpleLTInitiator1_dmi : void run() { - transaction_type trans; - phase_type phase; - sc_core::sc_time t; - - while (initTransaction(trans)) { - // Create transaction and initialise phase and t - phase = tlm::BEGIN_REQ; - t = sc_core::SC_ZERO_TIME; - - logStartTransation(trans); - - /////////////////////////////////////////////////////////// - // DMI handling: - // We use the DMI hint to check if it makes sense to ask for - // DMI pointers. The pattern is: - // - if the address is covered by a DMI region do a DMI access - // - otherwise do a normal transaction - // -> check if we get a DMI hint and acquire the DMI pointers if it is - // set - /////////////////////////////////////////////////////////// - - // Check if the address is covered by our DMI region - if ( (trans.get_address() >= mDMIData.get_start_address()) && - (trans.get_address() <= mDMIData.get_end_address()) ) { - // We can handle the data here. As the logEndTransaction is assuming - // something to happen in the data structure, we really need to - // do this: - trans.set_response_status(tlm::TLM_OK_RESPONSE); - sc_dt::uint64 tmp = trans.get_address() - mDMIData.get_start_address(); - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { - *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp] = mData; - - } else { - mData = *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp]; - } - - // Do the wait immediately. Note that doing the wait here eats almost - // all the performance anyway, so we only gain something if we're - // using temporal decoupling. - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { - wait(mDMIData.get_write_latency()); - - } else { - wait(mDMIData.get_read_latency()); - } - - logEndTransaction(trans); + // scope needed to free the memory of the local variables (co-routine stacks are not proper cleaned up at simulation end) + { + transaction_type trans; + phase_type phase; + sc_core::sc_time t; + + while (initTransaction(trans)) { + // Create transaction and initialise phase and t + phase = tlm::BEGIN_REQ; + t = sc_core::SC_ZERO_TIME; + + logStartTransation(trans); + + /////////////////////////////////////////////////////////// + // DMI handling: + // We use the DMI hint to check if it makes sense to ask for + // DMI pointers. The pattern is: + // - if the address is covered by a DMI region do a DMI access + // - otherwise do a normal transaction + // -> check if we get a DMI hint and acquire the DMI pointers if it is + // set + /////////////////////////////////////////////////////////// + + // Check if the address is covered by our DMI region + if ( (trans.get_address() >= mDMIData.get_start_address()) && + (trans.get_address() <= mDMIData.get_end_address()) ) { + // We can handle the data here. As the logEndTransaction is assuming + // something to happen in the data structure, we really need to + // do this: + trans.set_response_status(tlm::TLM_OK_RESPONSE); + sc_dt::uint64 tmp = trans.get_address() - mDMIData.get_start_address(); + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { + *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp] = mData; + + } else { + mData = *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp]; + } + // Do the wait immediately. Note that doing the wait here eats almost + // all the performance anyway, so we only gain something if we're + // using temporal decoupling. + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { + wait(mDMIData.get_write_latency()); + + } else { + wait(mDMIData.get_read_latency()); + } - } else { // we need a full transaction - sc_dt::uint64 addr = trans.get_address(); //Save address before it is mutated - socket->b_transport(trans, t); - wait(t); - logEndTransaction(trans); - - // Acquire DMI pointer on is available: - if (trans.is_dmi_allowed()) - { - dmi_type tmp; - tmp.init(); - trans.set_address(addr); //restore address, in case it was mutated. - trans.set_write(); - if ( socket->get_direct_mem_ptr(trans, tmp) - && tmp.is_write_allowed() ) - { - mDMIData = tmp; - } - } + logEndTransaction(trans); + + } else { // we need a full transaction + sc_dt::uint64 addr = trans.get_address(); //Save address before it is mutated + socket->b_transport(trans, t); + wait(t); + logEndTransaction(trans); + + // Acquire DMI pointer on is available: + if (trans.is_dmi_allowed()) + { + dmi_type tmp; + tmp.init(); + trans.set_address(addr); //restore address, in case it was mutated. + trans.set_write(); + if ( socket->get_direct_mem_ptr(trans, tmp) + && tmp.is_write_allowed() ) + { + mDMIData = tmp; + } + } + } } } wait(); diff --git a/tests/include/tlm/SimpleLTInitiator2.h b/tests/include/tlm/SimpleLTInitiator2.h index 4fa2903ca..f9b48ac57 100644 --- a/tests/include/tlm/SimpleLTInitiator2.h +++ b/tests/include/tlm/SimpleLTInitiator2.h @@ -119,19 +119,22 @@ class SimpleLTInitiator2 : public sc_core::sc_module void run() { - transaction_type trans; - sc_core::sc_time t; + // scope needed to free the memory of the local variables (co-routine stacks are not proper cleaned up at simulation end) + { + transaction_type trans; + sc_core::sc_time t; - while (initTransaction(trans)) { - // Create transaction and initialise t - t = sc_core::SC_ZERO_TIME; + while (initTransaction(trans)) { + // Create transaction and initialise t + t = sc_core::SC_ZERO_TIME; - logStartTransation(trans); + logStartTransation(trans); - socket->b_transport(trans, t); - wait(t); + socket->b_transport(trans, t); + wait(t); - logEndTransaction(trans); + logEndTransaction(trans); + } } wait(); diff --git a/tests/include/tlm/SimpleLTInitiator2_DMI.h b/tests/include/tlm/SimpleLTInitiator2_DMI.h index 7c5e4c463..c0091a148 100644 --- a/tests/include/tlm/SimpleLTInitiator2_DMI.h +++ b/tests/include/tlm/SimpleLTInitiator2_DMI.h @@ -141,75 +141,77 @@ class SimpleLTInitiator2_dmi : public sc_core::sc_module void run() { - transaction_type trans; - sc_core::sc_time t; - - while (initTransaction(trans)) { - // Create transaction and initialise t - t = sc_core::SC_ZERO_TIME; - - logStartTransation(trans); - - /////////////////////////////////////////////////////////// - // DMI handling: - // We do *not* use the DMI hint to check if it makes sense to ask for - // DMI pointers. So the pattern is: - // - if the address is not covered by a DMI region try to acquire DMI - // pointers - // - if we have a DMI pointer, do the DMI "transaction" - // - otherwise fall back to a normal transaction - /////////////////////////////////////////////////////////// - - std::pair& dmi_data = getDMIData(trans); - - // Check if we need to acquire a DMI pointer - if((trans.get_address() < dmi_data.first.get_start_address()) || - (trans.get_address() > dmi_data.first.get_end_address()) ) - { - sc_dt::uint64 address = trans.get_address(); //save original address - dmi_data.second = - socket->get_direct_mem_ptr(trans, - dmi_data.first); - trans.set_address(address); - } - // Do DMI "transaction" if we have a valid region - if (dmi_data.second && - (trans.get_address() >= dmi_data.first.get_start_address()) && - (trans.get_address() <= dmi_data.first.get_end_address()) ) - { - // We can handle the data here. As the logEndTransaction is assuming - // something to happen in the data structure, we really need to - // do this: - trans.set_response_status(tlm::TLM_OK_RESPONSE); - sc_dt::uint64 tmp = trans.get_address() - dmi_data.first.get_start_address(); - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) - { - *(unsigned int*)&dmi_data.first.get_dmi_ptr()[tmp] = mData; - } - else - { - mData = *(unsigned int*)&dmi_data.first.get_dmi_ptr()[tmp]; - } - - // Do the wait immediately. Note that doing the wait here eats almost - // all the performance anyway, so we only gain something if we're - // using temporal decoupling. - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { - wait(dmi_data.first.get_write_latency()); - - } else { - wait(dmi_data.first.get_read_latency()); - } - } - else // we need a full transaction - { - socket->b_transport(trans, t); - wait(t); + // scope needed to free the memory of the local variables (co-routine stacks are not proper cleaned up at simulation end) + { + transaction_type trans; + sc_core::sc_time t; + + while (initTransaction(trans)) { + // Create transaction and initialise t + t = sc_core::SC_ZERO_TIME; + + logStartTransation(trans); + + /////////////////////////////////////////////////////////// + // DMI handling: + // We do *not* use the DMI hint to check if it makes sense to ask for + // DMI pointers. So the pattern is: + // - if the address is not covered by a DMI region try to acquire DMI + // pointers + // - if we have a DMI pointer, do the DMI "transaction" + // - otherwise fall back to a normal transaction + /////////////////////////////////////////////////////////// + + std::pair& dmi_data = getDMIData(trans); + + // Check if we need to acquire a DMI pointer + if((trans.get_address() < dmi_data.first.get_start_address()) || + (trans.get_address() > dmi_data.first.get_end_address()) ) + { + sc_dt::uint64 address = trans.get_address(); //save original address + dmi_data.second = + socket->get_direct_mem_ptr(trans, + dmi_data.first); + trans.set_address(address); + } + // Do DMI "transaction" if we have a valid region + if (dmi_data.second && + (trans.get_address() >= dmi_data.first.get_start_address()) && + (trans.get_address() <= dmi_data.first.get_end_address()) ) + { + // We can handle the data here. As the logEndTransaction is assuming + // something to happen in the data structure, we really need to + // do this: + trans.set_response_status(tlm::TLM_OK_RESPONSE); + sc_dt::uint64 tmp = trans.get_address() - dmi_data.first.get_start_address(); + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) + { + *(unsigned int*)&dmi_data.first.get_dmi_ptr()[tmp] = mData; + } + else + { + mData = *(unsigned int*)&dmi_data.first.get_dmi_ptr()[tmp]; + } + + // Do the wait immediately. Note that doing the wait here eats almost + // all the performance anyway, so we only gain something if we're + // using temporal decoupling. + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { + wait(dmi_data.first.get_write_latency()); + + } else { + wait(dmi_data.first.get_read_latency()); + } + } + else // we need a full transaction + { + socket->b_transport(trans, t); + wait(t); + } + logEndTransaction(trans); } - logEndTransaction(trans); } wait(); - } // Invalidate DMI pointer(s) diff --git a/tests/include/tlm/SimpleLTInitiator3.h b/tests/include/tlm/SimpleLTInitiator3.h index 13a9491c9..74b4c1e42 100644 --- a/tests/include/tlm/SimpleLTInitiator3.h +++ b/tests/include/tlm/SimpleLTInitiator3.h @@ -119,20 +119,22 @@ class SimpleLTInitiator3 : public sc_core::sc_module void run() { - transaction_type trans; - sc_core::sc_time t; + { + transaction_type trans; + sc_core::sc_time t; - while (initTransaction(trans)) { - // Create transaction and initialise t - t = sc_core::SC_ZERO_TIME; + while (initTransaction(trans)) { + // Create transaction and initialise t + t = sc_core::SC_ZERO_TIME; - logStartTransation(trans); + logStartTransation(trans); - socket->b_transport(trans, t); - // Transaction Finished, wait for the returned delay - wait(t); + socket->b_transport(trans, t); + // Transaction Finished, wait for the returned delay + wait(t); - logEndTransaction(trans); + logEndTransaction(trans); + } } wait(); diff --git a/tests/include/tlm/SimpleLTInitiator3_DMI.h b/tests/include/tlm/SimpleLTInitiator3_DMI.h index be8fa006b..0556e7116 100644 --- a/tests/include/tlm/SimpleLTInitiator3_DMI.h +++ b/tests/include/tlm/SimpleLTInitiator3_DMI.h @@ -139,78 +139,80 @@ class SimpleLTInitiator3_dmi : public sc_core::sc_module void run() { - transaction_type trans; - sc_core::sc_time t; - - while (initTransaction(trans)) { - // Create transaction and initialise t - t = sc_core::SC_ZERO_TIME; - - logStartTransation(trans); - - /////////////////////////////////////////////////////////// - // DMI handling: - // We do *not* use the DMI hint to check if it makes sense to ask for - // DMI pointers. So the pattern is: - // - if the address is not covered by a DMI region try to acquire DMI - // pointers - // - if we have a DMI pointer, do the DMI "transaction" - // - otherwise fall back to a normal transaction - /////////////////////////////////////////////////////////// - - std::pair& dmi_data = getDMIData(trans); - - // Check if we need to acquire a DMI pointer - if((trans.get_address() < dmi_data.first.get_start_address()) || - (trans.get_address() > dmi_data.first.get_end_address()) ) - { - sc_dt::uint64 address = trans.get_address(); //save original address - dmi_data.second = - socket->get_direct_mem_ptr(trans, - dmi_data.first); - trans.set_address(address); + // scope needed to free the memory of the local variables (co-routine stacks are not proper cleaned up at simulation end) + { + transaction_type trans; + sc_core::sc_time t; + + while (initTransaction(trans)) { + // Create transaction and initialise t + t = sc_core::SC_ZERO_TIME; + + logStartTransation(trans); + + /////////////////////////////////////////////////////////// + // DMI handling: + // We do *not* use the DMI hint to check if it makes sense to ask for + // DMI pointers. So the pattern is: + // - if the address is not covered by a DMI region try to acquire DMI + // pointers + // - if we have a DMI pointer, do the DMI "transaction" + // - otherwise fall back to a normal transaction + /////////////////////////////////////////////////////////// + + std::pair& dmi_data = getDMIData(trans); + + // Check if we need to acquire a DMI pointer + if((trans.get_address() < dmi_data.first.get_start_address()) || + (trans.get_address() > dmi_data.first.get_end_address()) ) + { + sc_dt::uint64 address = trans.get_address(); //save original address + dmi_data.second = + socket->get_direct_mem_ptr(trans, + dmi_data.first); + trans.set_address(address); + } + // Do DMI "transaction" if we have a valid region + if (dmi_data.second && + (trans.get_address() >= dmi_data.first.get_start_address()) && + (trans.get_address() <= dmi_data.first.get_end_address()) ) + { + // We can handle the data here. As the logEndTransaction is assuming + // something to happen in the data structure, we really need to + // do this: + trans.set_response_status(tlm::TLM_OK_RESPONSE); + + sc_dt::uint64 tmp = trans.get_address() - dmi_data.first.get_start_address(); + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) + { + *(unsigned int*)&dmi_data.first.get_dmi_ptr()[tmp] = mData; + } + else + { + mData = *(unsigned int*)&dmi_data.first.get_dmi_ptr()[tmp]; + } + + // Do the wait immediately. Note that doing the wait here eats almost + // all the performance anyway, so we only gain something if we're + // using temporal decoupling. + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { + wait(dmi_data.first.get_write_latency()); + + } else { + wait(dmi_data.first.get_read_latency()); + } + } + else // we need a full transaction + { + socket->b_transport(trans, t); + // wait for the returned delay + wait(t); + } + + logEndTransaction(trans); } - // Do DMI "transaction" if we have a valid region - if (dmi_data.second && - (trans.get_address() >= dmi_data.first.get_start_address()) && - (trans.get_address() <= dmi_data.first.get_end_address()) ) - { - // We can handle the data here. As the logEndTransaction is assuming - // something to happen in the data structure, we really need to - // do this: - trans.set_response_status(tlm::TLM_OK_RESPONSE); - - sc_dt::uint64 tmp = trans.get_address() - dmi_data.first.get_start_address(); - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) - { - *(unsigned int*)&dmi_data.first.get_dmi_ptr()[tmp] = mData; - } - else - { - mData = *(unsigned int*)&dmi_data.first.get_dmi_ptr()[tmp]; - } - - // Do the wait immediately. Note that doing the wait here eats almost - // all the performance anyway, so we only gain something if we're - // using temporal decoupling. - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { - wait(dmi_data.first.get_write_latency()); - - } else { - wait(dmi_data.first.get_read_latency()); - } - } - else // we need a full transaction - { - socket->b_transport(trans, t); - // wait for the returned delay - wait(t); - } - - logEndTransaction(trans); } wait(); - } // Invalidate DMI pointer(s) diff --git a/tests/systemc/1666-2011-compliance/child_proc_control/child_proc_control.cpp b/tests/systemc/1666-2011-compliance/child_proc_control/child_proc_control.cpp index ab70ac7b9..07d2290b9 100644 --- a/tests/systemc/1666-2011-compliance/child_proc_control/child_proc_control.cpp +++ b/tests/systemc/1666-2011-compliance/child_proc_control/child_proc_control.cpp @@ -63,6 +63,17 @@ struct Top: sc_module c0[i] = c1[i] = c2[i] = c3[i] = c4[i] = c5[i] = 0; } } + + virtual ~Top() + { + delete [] given_birth; + delete [] c0; + delete [] c1; + delete [] c2; + delete [] c3; + delete [] c4; + delete [] c5; + } int count; int f0, f1; diff --git a/tests/systemc/1666-2011-compliance/event_list/event_list.cpp b/tests/systemc/1666-2011-compliance/event_list/event_list.cpp index 9b92438de..3893888a5 100644 --- a/tests/systemc/1666-2011-compliance/event_list/event_list.cpp +++ b/tests/systemc/1666-2011-compliance/event_list/event_list.cpp @@ -42,6 +42,7 @@ using std::endl; struct Mod: sc_module { sc_port, 0> p; // Multiport + sc_event_or_list all_events; Mod(sc_module_name _name) { @@ -53,7 +54,7 @@ struct Mod: sc_module { for (;;) { - wait(all_events()); + wait(all_events); cout << "M::T1 awoke with " << p[0]->read() << p[1]->read() << p[2]->read() << " at " << sc_time_stamp() << " on list" << endl; } @@ -67,30 +68,29 @@ struct Mod: sc_module << " at " << sc_time_stamp() << " on list" << endl; } } - sc_event_or_list all_events() const + + void end_of_elaboration() override { sc_assert( p.size() == 3 ); - sc_event_or_list or_list; for (int i = 0; i < p.size(); i++) - or_list |= p[i]->default_event(); + all_events |= p[i]->default_event(); - sc_assert( or_list.size() == 3 ); - return or_list; - } + sc_assert( all_events.size() == 3 ); + } }; struct Top: sc_module { Top(sc_module_name _name) - : finished(false) + : m("m") + , finished(false) , count(0) { - m = new Mod("m"); - m->p.bind(sig1); - m->p.bind(sig2); - m->p.bind(sig3); + m.p.bind(sig1); + m.p.bind(sig2); + m.p.bind(sig3); SC_THREAD(T); SC_METHOD(M); } @@ -105,7 +105,7 @@ struct Top: sc_module } sc_signal sig1, sig2, sig3; - Mod* m; + Mod m; sc_event e1, e2, e3, e4; bool finished; diff --git a/tests/systemc/1666-2011-compliance/method_with_reset/method_with_reset.cpp b/tests/systemc/1666-2011-compliance/method_with_reset/method_with_reset.cpp index cf1fb49ab..ebaff00a1 100644 --- a/tests/systemc/1666-2011-compliance/method_with_reset/method_with_reset.cpp +++ b/tests/systemc/1666-2011-compliance/method_with_reset/method_with_reset.cpp @@ -356,10 +356,12 @@ struct Top: sc_module event_list = m3.reset_event() | m3.terminated_event(); next_trigger(event_list); } + + // outside the SC_THREAD to make the leak sanitizer happy (co-routine stacks are not proper cleaned up) + sc_event_or_list or_list; void multiple_reset_handler() { - sc_event_or_list or_list; or_list |= m1.reset_event(); or_list |= m2.reset_event(); or_list |= m3.reset_event(); diff --git a/tests/systemc/1666-2011-compliance/mixed_child_procs/mixed_child_procs.cpp b/tests/systemc/1666-2011-compliance/mixed_child_procs/mixed_child_procs.cpp index 2e7a6f695..e54ce543d 100644 --- a/tests/systemc/1666-2011-compliance/mixed_child_procs/mixed_child_procs.cpp +++ b/tests/systemc/1666-2011-compliance/mixed_child_procs/mixed_child_procs.cpp @@ -77,6 +77,17 @@ struct Top: sc_module m = sc_spawn(sc_bind(&Top::child_method, this, index++, 3), "m", &opt); } + virtual ~Top() + { + delete [] given_birth; + delete [] f0; + delete [] f1; + delete [] f2; + delete [] f3; + delete [] f4; + delete [] f5; + } + sc_spawn_options opt; sc_process_handle t, m; std::exception ex; diff --git a/tests/systemc/1666-2011-compliance/sc_vector/sc_vector.cpp b/tests/systemc/1666-2011-compliance/sc_vector/sc_vector.cpp index 55cfd31b9..ad1eee047 100644 --- a/tests/systemc/1666-2011-compliance/sc_vector/sc_vector.cpp +++ b/tests/systemc/1666-2011-compliance/sc_vector/sc_vector.cpp @@ -323,9 +323,10 @@ struct Top: sc_module SC_THREAD(T2); } + // outside the SC_THREAD to make the leak sanitizer happy (co-routine stacks are not proper cleaned up) + sc_event_or_list list; void T() { - sc_event_or_list list; for (int i = 0; i < 4; i++) list |= sigs[i].default_event(); for (;;) diff --git a/tests/systemc/compliance_1666/test001/test001.cpp b/tests/systemc/compliance_1666/test001/test001.cpp index 429e7c21b..9e02c543a 100644 --- a/tests/systemc/compliance_1666/test001/test001.cpp +++ b/tests/systemc/compliance_1666/test001/test001.cpp @@ -254,6 +254,11 @@ SC_MODULE(Top) sensitive << *pp; //// Sensitivity separated from SC_METHOD DOULOS011 } + virtual ~Nested() + { + delete pp; + } + void action() { op = sc_min(3, (*pp).read() + 1); } sc_out op; //// Out-of-order declaration DOULOS052 @@ -308,6 +313,19 @@ SC_MODULE(Top) modb.p4(ms); } + virtual ~Top() + { + delete link->link->m; + delete link->link->s; + delete link->link->p; + delete link->link; + delete link->p; + delete link->m; + delete link; + + delete sig; + } + }; diff --git a/tests/systemc/compliance_1666/test234/test234.cpp b/tests/systemc/compliance_1666/test234/test234.cpp index 1b5467544..08fbf5b40 100644 --- a/tests/systemc/compliance_1666/test234/test234.cpp +++ b/tests/systemc/compliance_1666/test234/test234.cpp @@ -19,10 +19,17 @@ struct Chan: i_f, sc_object struct Port: sc_port { - sc_event_finder& find_event() const + Port() : m_event_finder(*this, &i_f::event) { - return *new sc_event_finder_t( *this, &i_f::event ); } + + sc_event_finder& find_event() + { + return m_event_finder; + } + + private: + sc_event_finder_t m_event_finder; }; SC_MODULE(M) @@ -75,14 +82,13 @@ SC_MODULE(M) SC_MODULE(Top) { - M *m; + M m; Chan chan1, chan2, chan3; - SC_CTOR(Top) + SC_CTOR(Top): m("m") { - m = new M("m"); - m->mp(chan1); - m->mp(chan2); - m->mp(chan3); + m.mp(chan1); + m.mp(chan2); + m.mp(chan3); SC_THREAD(T); } void T() diff --git a/tests/systemc/kernel/dynamic_processes/test06/test06.cpp b/tests/systemc/kernel/dynamic_processes/test06/test06.cpp index dd5e91691..eca816aef 100644 --- a/tests/systemc/kernel/dynamic_processes/test06/test06.cpp +++ b/tests/systemc/kernel/dynamic_processes/test06/test06.cpp @@ -49,21 +49,21 @@ void p3() { void p2() { cerr << sc_time_stamp() << ":entering p2, spawning p3" << endl; - sc_spawn(sc_bind(&p3)); + sc_spawn(&p3); wait(20, SC_NS); cerr << sc_time_stamp() << ":exiting p2" << endl; } void p1() { cerr << sc_time_stamp() << ":entering p1, spawning p2" << endl; - sc_spawn(sc_bind(&p2)); + sc_spawn(&p2); wait(10, SC_NS); cerr << sc_time_stamp() << ":exiting p1" << endl; } void p0() { cerr << sc_time_stamp() << ":entering p0, spawning p1" << endl; - sc_spawn(sc_bind(&p1)); + sc_spawn(&p1); cerr << sc_time_stamp() << ":exiting p0" << endl; } diff --git a/tests/systemc/kernel/dynamic_processes/test07/test07.cpp b/tests/systemc/kernel/dynamic_processes/test07/test07.cpp index 76f7f48c1..fa4925e80 100644 --- a/tests/systemc/kernel/dynamic_processes/test07/test07.cpp +++ b/tests/systemc/kernel/dynamic_processes/test07/test07.cpp @@ -60,13 +60,17 @@ SC_MODULE(DUT) { cout << sc_time_stamp() << " callback" << endl; } + void thread() { - sc_spawn_options options; - options.spawn_method(); - options.set_sensitivity( &m_port ); - options.dont_initialize(); - sc_spawn( sc_bind(&DUT::method,this), "method", &options ); + // scope to free `options` memory (co-routine stacks are not proper cleaned up at simulation end) + { + sc_spawn_options options; + options.spawn_method(); + options.set_sensitivity( &m_port ); + options.dont_initialize(); + sc_spawn( sc_bind(&DUT::method,this), "method", &options ); + } for ( bool value=true;; value = !value) { wait(); diff --git a/tests/systemc/kernel/process_control/test06/test06.cpp b/tests/systemc/kernel/process_control/test06/test06.cpp index 7149c6944..01547ef5f 100644 --- a/tests/systemc/kernel/process_control/test06/test06.cpp +++ b/tests/systemc/kernel/process_control/test06/test06.cpp @@ -84,32 +84,35 @@ SC_MODULE(top) { wait(); - // copy children (needed, since children may get reordered) - std::vector< sc_object* > children = - sc_get_current_process_handle().get_child_objects(); + // scope needed to free the memory of the local variables (co-routine stacks are not proper cleaned up at simulation end) + { + // copy children (needed, since children may get reordered) + std::vector< sc_object* > children = + sc_get_current_process_handle().get_child_objects(); - std::vector< sc_object* >::const_iterator it = children.begin(); + std::vector< sc_object* >::const_iterator it = children.begin(); - while( it != children.end() ) - { - sc_process_handle h( *it++ ); - sc_assert( h.valid() ); + while( it != children.end() ) + { + sc_process_handle h( *it++ ); + sc_assert( h.valid() ); - std::cout << h.name() << " " - << "kill requested " - << "(" << h.get_process_object()->kind() << ") " - << "(" << sc_time_stamp() << " @ " << sc_delta_count() << ")" - << std::endl; + std::cout << h.name() << " " + << "kill requested " + << "(" << h.get_process_object()->kind() << ") " + << "(" << sc_time_stamp() << " @ " << sc_delta_count() << ")" + << std::endl; - h.kill( SC_INCLUDE_DESCENDANTS ); - } + h.kill( SC_INCLUDE_DESCENDANTS ); + } - wait(); + wait(); - std::cout << sc_get_current_process_handle().name() - << " ended " - << "(" << sc_time_stamp() << " @ " << sc_delta_count() << ")" - << std::endl; + std::cout << sc_get_current_process_handle().name() + << " ended " + << "(" << sc_time_stamp() << " @ " << sc_delta_count() << ")" + << std::endl; + } wait(); sc_stop(); diff --git a/tests/systemc/kernel/sc_event/test15/event_triggered.cpp b/tests/systemc/kernel/sc_event/test15/event_triggered.cpp index 200f2739e..adba37565 100644 --- a/tests/systemc/kernel/sc_event/test15/event_triggered.cpp +++ b/tests/systemc/kernel/sc_event/test15/event_triggered.cpp @@ -117,9 +117,11 @@ SC_MODULE( module ) CHECK( event_return.triggered() ); } + // outside the SC_THREAD to make the leak sanitizer happy (co-routine stacks are not proper cleaned up) + sc_event_or_list events_or; // even events only + void consumer_dynamic() { - sc_event_or_list events_or; // even events only for(unsigned i = 0; i < events.size(); i+=2) events_or |= events[i]; diff --git a/tests/systemc/misc/ieee1666_2023/5.7.3/5.7.3.cpp b/tests/systemc/misc/ieee1666_2023/5.7.3/5.7.3.cpp index ca23a0ae9..7a6ce861a 100644 --- a/tests/systemc/misc/ieee1666_2023/5.7.3/5.7.3.cpp +++ b/tests/systemc/misc/ieee1666_2023/5.7.3/5.7.3.cpp @@ -43,9 +43,12 @@ class chan_class : public if_class, public sc_core::sc_prim_channel { template class port_class : public sc_core::sc_port { public: - sc_core::sc_event_finder& event_finder() const { - return *new sc_core::sc_event_finder_t(*this, &if_class::ev_func); + port_class() : m_event_finder(*this, &if_class::ev_func) {} + sc_core::sc_event_finder& event_finder() { + return m_event_finder; } + private: + sc_core::sc_event_finder_t m_event_finder; }; SC_MODULE(mod_class) diff --git a/tests/systemc/misc/sim_tests/biquad/biquad2/delay_line.h b/tests/systemc/misc/sim_tests/biquad/biquad2/delay_line.h index 60d9805e8..830938cba 100644 --- a/tests/systemc/misc/sim_tests/biquad/biquad2/delay_line.h +++ b/tests/systemc/misc/sim_tests/biquad/biquad2/delay_line.h @@ -64,6 +64,10 @@ SC_MODULE( delay_line ) line = new float[delay]; } + virtual ~delay_line() { + delete[] line; + } + // Process functionality in member function below void entry(); }; diff --git a/tests/systemc/misc/sim_tests/biquad/biquad2/op_queue.h b/tests/systemc/misc/sim_tests/biquad/biquad2/op_queue.h index 63465262a..f4a862426 100644 --- a/tests/systemc/misc/sim_tests/biquad/biquad2/op_queue.h +++ b/tests/systemc/misc/sim_tests/biquad/biquad2/op_queue.h @@ -69,6 +69,10 @@ SC_MODULE( op_queue ) queue = new float[queue_size]; } + virtual ~op_queue() { + delete[] queue; + } + // Process functionality in member function below void entry(); }; diff --git a/tests/systemc/misc/sim_tests/biquad/biquad3/delay_line.h b/tests/systemc/misc/sim_tests/biquad/biquad3/delay_line.h index c99e1076c..b0c0b4623 100644 --- a/tests/systemc/misc/sim_tests/biquad/biquad3/delay_line.h +++ b/tests/systemc/misc/sim_tests/biquad/biquad3/delay_line.h @@ -65,6 +65,10 @@ SC_MODULE( delay_line ) sensitive << in; } + virtual ~delay_line() { + delete[] line; + } + // Process functionality in member function below void entry(); }; diff --git a/tests/systemc/misc/sim_tests/simple_cpu/simple_cpu.cpp b/tests/systemc/misc/sim_tests/simple_cpu/simple_cpu.cpp index 86a538688..774c64130 100644 --- a/tests/systemc/misc/sim_tests/simple_cpu/simple_cpu.cpp +++ b/tests/systemc/misc/sim_tests/simple_cpu/simple_cpu.cpp @@ -86,6 +86,10 @@ SC_MODULE( exec_decode ) program_counter.write(pc); } + virtual ~exec_decode() { + delete[] data_mem; + } + // Functionality void entry(); }; @@ -223,6 +227,10 @@ SC_MODULE( fetch ) instruction.write(0); } + virtual ~fetch() { + delete[] prog_mem; + } + // Functionality void entry(); }; diff --git a/tests/tlm/cancel_all/cancel_all.cpp b/tests/tlm/cancel_all/cancel_all.cpp index d3b85b715..6c0f973ed 100644 --- a/tests/tlm/cancel_all/cancel_all.cpp +++ b/tests/tlm/cancel_all/cancel_all.cpp @@ -21,11 +21,12 @@ SC_MODULE(Test_peq_with_cb) { section = 1; - tlm::tlm_generic_payload *trans; + std::vector trans_vec; tlm::tlm_phase phase; for (int i = 0; i < 50; i++) { - trans = new tlm::tlm_generic_payload; + tlm::tlm_generic_payload *trans = new tlm::tlm_generic_payload; + trans_vec.push_back(trans); trans->set_address(i); m_peq.notify( *trans, phase, sc_time(rand() % 100, SC_NS) ); } @@ -33,17 +34,29 @@ SC_MODULE(Test_peq_with_cb) m_peq.cancel_all(); cout << "cancel_all\n"; + while(!trans_vec.empty()) + { + delete trans_vec.back(); + trans_vec.pop_back(); + } + section = 2; for (int i = 100; i < 150; i++) { - trans = new tlm::tlm_generic_payload; + tlm::tlm_generic_payload *trans = new tlm::tlm_generic_payload; + trans_vec.push_back(trans); trans->set_address(i); m_peq.notify( *trans, phase, sc_time(rand() % 100, SC_NS) ); } wait(50, SC_NS); m_peq.cancel_all(); cout << "cancel_all\n"; + while(!trans_vec.empty()) + { + delete trans_vec.back(); + trans_vec.pop_back(); + } wait(50, SC_NS); } @@ -91,10 +104,11 @@ SC_MODULE(Test_peq_with_get) section = 3; - tlm::tlm_generic_payload *trans; + std::vector trans_vec; for (int i = 0; i < 50; i++) { - trans = new tlm::tlm_generic_payload; + tlm::tlm_generic_payload *trans = new tlm::tlm_generic_payload; + trans_vec.push_back(trans); trans->set_address(i); m_peq.notify( *trans, sc_time(rand() % 100, SC_NS) ); } @@ -102,17 +116,28 @@ SC_MODULE(Test_peq_with_get) m_peq.cancel_all(); cout << "cancel_all\n"; + while(!trans_vec.empty()) + { + delete trans_vec.back(); + trans_vec.pop_back(); + } section = 4; for (int i = 100; i < 150; i++) { - trans = new tlm::tlm_generic_payload; + tlm::tlm_generic_payload *trans = new tlm::tlm_generic_payload; + trans_vec.push_back(trans); trans->set_address(i); m_peq.notify( *trans, sc_time(rand() % 100, SC_NS) ); } wait(50, SC_NS); m_peq.cancel_all(); cout << "cancel_all\n"; + while(!trans_vec.empty()) + { + delete trans_vec.back(); + trans_vec.pop_back(); + } wait(50, SC_NS); } diff --git a/tests/tlm/nb2b_adapter/mm.h b/tests/tlm/nb2b_adapter/mm.h index c91dd3ed5..5cea080fe 100644 --- a/tests/tlm/nb2b_adapter/mm.h +++ b/tests/tlm/nb2b_adapter/mm.h @@ -18,6 +18,7 @@ class mm: public tlm::tlm_mm_interface while (free_list) { + access* node = free_list; ptr = free_list->trans; // Delete generic payload and all extensions @@ -25,6 +26,7 @@ class mm: public tlm::tlm_mm_interface delete ptr; free_list = free_list->next; + delete node; } while (empties) diff --git a/tests/tlm/nb2b_adapter/nb2b_adapter.cpp b/tests/tlm/nb2b_adapter/nb2b_adapter.cpp index 32e58f1f6..923d67b4a 100644 --- a/tests/tlm/nb2b_adapter/nb2b_adapter.cpp +++ b/tests/tlm/nb2b_adapter/nb2b_adapter.cpp @@ -235,8 +235,12 @@ struct Interconnect: sc_module if (status == tlm::TLM_COMPLETED) { - accessor(trans).clear_extension(ext); - delete ext; + accessor(trans).get_extension(ext); + if (ext) + { + accessor(trans).clear_extension(ext); + delete ext; + } } return status; @@ -341,24 +345,23 @@ struct Target: sc_module SC_MODULE(Top) { - Initiator *initiator1; - Initiator *initiator2; - Interconnect *interconnect; - Target *target1; - Target *target2; + Initiator initiator1; + Initiator initiator2; + Interconnect interconnect; + Target target1; + Target target2; SC_CTOR(Top) + : initiator1("initiator1") + , initiator2("initiator2") + , interconnect("interconnect", 1) + , target1("target1") + , target2("target2") { - initiator1 = new Initiator ("initiator1"); - initiator2 = new Initiator ("initiator2"); - interconnect = new Interconnect("interconnect", 1); - target1 = new Target ("target1"); - target2 = new Target ("target2"); - - initiator1->socket.bind(interconnect->targ_socket); - initiator2->socket.bind(interconnect->targ_socket); - interconnect->init_socket.bind(target1->socket); - interconnect->init_socket.bind(target2->socket); + initiator1.socket.bind(interconnect.targ_socket); + initiator2.socket.bind(interconnect.targ_socket); + interconnect.init_socket.bind(target1.socket); + interconnect.init_socket.bind(target2.socket); } }; diff --git a/tests/tlm/static_extensions/ext2gp/SimpleLTInitiator_ext.h b/tests/tlm/static_extensions/ext2gp/SimpleLTInitiator_ext.h index f9875d907..083a75109 100644 --- a/tests/tlm/static_extensions/ext2gp/SimpleLTInitiator_ext.h +++ b/tests/tlm/static_extensions/ext2gp/SimpleLTInitiator_ext.h @@ -131,99 +131,99 @@ class SimpleLTInitiator_ext : public sc_core::sc_module void run() { - transaction_type trans; - phase_type phase; - sc_core::sc_time t; - // make sure that our transaction has the proper extension: - my_extension* tmp_ext = new my_extension(); - tmp_ext->m_data = 11; - - trans.set_extension(tmp_ext); - - while (initTransaction(trans)) - { - // Create transaction and initialise phase and t - phase = tlm::BEGIN_REQ; - t = sc_core::SC_ZERO_TIME; - - logStartTransation(trans); - /////////////////////////////////////////////////////////// - // DMI handling: - // We use the DMI hint to check if it makes sense to ask for - // DMI pointers. The pattern is: - // - if the address is covered by a DMI region do a DMI access - // - otherwise do a normal transaction - // -> check if we get a DMI hint and acquire the DMI pointers if it - // is set - /////////////////////////////////////////////////////////// - - // Check if the address is covered by our DMI region - if ( (trans.get_address() >= mDMIData.get_start_address()) && - (trans.get_address() <= mDMIData.get_end_address()) ) - { - // We can handle the data here. As the logEndTransaction is - // assuming something to happen in the data structure, we really - // need to do this: - trans.set_response_status(tlm::TLM_OK_RESPONSE); - sc_dt::uint64 tmp = trans.get_address() - mDMIData.get_start_address(); - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { - *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp] = mData; - - } else { - mData = *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp]; - } - - // Do the wait immediately. Note that doing the wait here eats - // almost all the performance anyway, so we only gain something - // if we're using temporal decoupling. - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { - wait(mDMIData.get_write_latency()); - - } else { - wait(mDMIData.get_read_latency()); - } + { + transaction_type trans; + phase_type phase; + sc_core::sc_time t; + // make sure that our transaction has the proper extension: + my_extension* tmp_ext = new my_extension(); + tmp_ext->m_data = 11; - logEndTransaction(trans); - - } else { // we need a full transaction - switch (socket->nb_transport_fw(trans, phase, t)) { - case tlm::TLM_COMPLETED: - // Transaction Finished, wait for the returned delay - wait(t); - break; - - case tlm::TLM_ACCEPTED: - case tlm::TLM_UPDATED: - // Transaction not yet finished, wait for the end of it - wait(mEndEvent); - break; - - default: - sc_assert(0); exit(1); - }; - - logEndTransaction(trans); + trans.set_extension(tmp_ext); - // Acquire DMI pointer if one is available: - if (trans.is_dmi_allowed()) - { - trans.set_write(); - dmi_type tmp; - if (socket->get_direct_mem_ptr(trans, - tmp)) - { - // FIXME: No support for separate read/write ranges - sc_assert(tmp.is_read_write_allowed()); - mDMIData = tmp; - } - } - } - } - delete tmp_ext; - wait(); - + while (initTransaction(trans)) + { + // Create transaction and initialise phase and t + phase = tlm::BEGIN_REQ; + t = sc_core::SC_ZERO_TIME; + + logStartTransation(trans); + /////////////////////////////////////////////////////////// + // DMI handling: + // We use the DMI hint to check if it makes sense to ask for + // DMI pointers. The pattern is: + // - if the address is covered by a DMI region do a DMI access + // - otherwise do a normal transaction + // -> check if we get a DMI hint and acquire the DMI pointers if it + // is set + /////////////////////////////////////////////////////////// + + // Check if the address is covered by our DMI region + if ( (trans.get_address() >= mDMIData.get_start_address()) && + (trans.get_address() <= mDMIData.get_end_address()) ) + { + // We can handle the data here. As the logEndTransaction is + // assuming something to happen in the data structure, we really + // need to do this: + trans.set_response_status(tlm::TLM_OK_RESPONSE); + sc_dt::uint64 tmp = trans.get_address() - mDMIData.get_start_address(); + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { + *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp] = mData; + + } else { + mData = *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp]; + } + + // Do the wait immediately. Note that doing the wait here eats + // almost all the performance anyway, so we only gain something + // if we're using temporal decoupling. + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { + wait(mDMIData.get_write_latency()); + + } else { + wait(mDMIData.get_read_latency()); + } + + logEndTransaction(trans); + + } else { // we need a full transaction + switch (socket->nb_transport_fw(trans, phase, t)) { + case tlm::TLM_COMPLETED: + // Transaction Finished, wait for the returned delay + wait(t); + break; + + case tlm::TLM_ACCEPTED: + case tlm::TLM_UPDATED: + // Transaction not yet finished, wait for the end of it + wait(mEndEvent); + break; + + default: + sc_assert(0); exit(1); + }; + + logEndTransaction(trans); + + // Acquire DMI pointer if one is available: + if (trans.is_dmi_allowed()) + { + trans.set_write(); + dmi_type tmp; + if (socket->get_direct_mem_ptr(trans, + tmp)) + { + // FIXME: No support for separate read/write ranges + sc_assert(tmp.is_read_write_allowed()); + mDMIData = tmp; + } + } + } + } + } + wait(); } - + sync_enum_type myNBTransport(transaction_type& trans, phase_type& phase, sc_core::sc_time& t) diff --git a/tests/tlm/static_extensions/ext2gp2ext/SimpleLTInitiator_ext.h b/tests/tlm/static_extensions/ext2gp2ext/SimpleLTInitiator_ext.h index f9875d907..0b7863b11 100644 --- a/tests/tlm/static_extensions/ext2gp2ext/SimpleLTInitiator_ext.h +++ b/tests/tlm/static_extensions/ext2gp2ext/SimpleLTInitiator_ext.h @@ -131,97 +131,98 @@ class SimpleLTInitiator_ext : public sc_core::sc_module void run() { - transaction_type trans; - phase_type phase; - sc_core::sc_time t; - // make sure that our transaction has the proper extension: - my_extension* tmp_ext = new my_extension(); - tmp_ext->m_data = 11; - - trans.set_extension(tmp_ext); - - while (initTransaction(trans)) + // scope needed to free the memory of the local variables (co-routine stacks are not proper cleaned up at simulation end) { - // Create transaction and initialise phase and t - phase = tlm::BEGIN_REQ; - t = sc_core::SC_ZERO_TIME; - - logStartTransation(trans); - /////////////////////////////////////////////////////////// - // DMI handling: - // We use the DMI hint to check if it makes sense to ask for - // DMI pointers. The pattern is: - // - if the address is covered by a DMI region do a DMI access - // - otherwise do a normal transaction - // -> check if we get a DMI hint and acquire the DMI pointers if it - // is set - /////////////////////////////////////////////////////////// - - // Check if the address is covered by our DMI region - if ( (trans.get_address() >= mDMIData.get_start_address()) && - (trans.get_address() <= mDMIData.get_end_address()) ) + transaction_type trans; + phase_type phase; + sc_core::sc_time t; + // make sure that our transaction has the proper extension: + my_extension* tmp_ext = new my_extension(); + tmp_ext->m_data = 11; + + trans.set_extension(tmp_ext); + + while (initTransaction(trans)) { - // We can handle the data here. As the logEndTransaction is - // assuming something to happen in the data structure, we really - // need to do this: - trans.set_response_status(tlm::TLM_OK_RESPONSE); - sc_dt::uint64 tmp = trans.get_address() - mDMIData.get_start_address(); - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { - *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp] = mData; - - } else { - mData = *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp]; - } - - // Do the wait immediately. Note that doing the wait here eats - // almost all the performance anyway, so we only gain something - // if we're using temporal decoupling. - if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { - wait(mDMIData.get_write_latency()); - - } else { - wait(mDMIData.get_read_latency()); - } + // Create transaction and initialise phase and t + phase = tlm::BEGIN_REQ; + t = sc_core::SC_ZERO_TIME; - logEndTransaction(trans); - - } else { // we need a full transaction - switch (socket->nb_transport_fw(trans, phase, t)) { - case tlm::TLM_COMPLETED: - // Transaction Finished, wait for the returned delay - wait(t); - break; - - case tlm::TLM_ACCEPTED: - case tlm::TLM_UPDATED: - // Transaction not yet finished, wait for the end of it - wait(mEndEvent); - break; - - default: - sc_assert(0); exit(1); - }; - - logEndTransaction(trans); + logStartTransation(trans); + /////////////////////////////////////////////////////////// + // DMI handling: + // We use the DMI hint to check if it makes sense to ask for + // DMI pointers. The pattern is: + // - if the address is covered by a DMI region do a DMI access + // - otherwise do a normal transaction + // -> check if we get a DMI hint and acquire the DMI pointers if it + // is set + /////////////////////////////////////////////////////////// - // Acquire DMI pointer if one is available: - if (trans.is_dmi_allowed()) + // Check if the address is covered by our DMI region + if ( (trans.get_address() >= mDMIData.get_start_address()) && + (trans.get_address() <= mDMIData.get_end_address()) ) { - trans.set_write(); - dmi_type tmp; - if (socket->get_direct_mem_ptr(trans, - tmp)) + // We can handle the data here. As the logEndTransaction is + // assuming something to happen in the data structure, we really + // need to do this: + trans.set_response_status(tlm::TLM_OK_RESPONSE); + sc_dt::uint64 tmp = trans.get_address() - mDMIData.get_start_address(); + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { + *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp] = mData; + + } else { + mData = *(unsigned int*)&mDMIData.get_dmi_ptr()[tmp]; + } + + // Do the wait immediately. Note that doing the wait here eats + // almost all the performance anyway, so we only gain something + // if we're using temporal decoupling. + if (trans.get_command() == tlm::TLM_WRITE_COMMAND) { + wait(mDMIData.get_write_latency()); + + } else { + wait(mDMIData.get_read_latency()); + } + + logEndTransaction(trans); + + } else { // we need a full transaction + switch (socket->nb_transport_fw(trans, phase, t)) { + case tlm::TLM_COMPLETED: + // Transaction Finished, wait for the returned delay + wait(t); + break; + + case tlm::TLM_ACCEPTED: + case tlm::TLM_UPDATED: + // Transaction not yet finished, wait for the end of it + wait(mEndEvent); + break; + + default: + sc_assert(0); exit(1); + }; + + logEndTransaction(trans); + + // Acquire DMI pointer if one is available: + if (trans.is_dmi_allowed()) { - // FIXME: No support for separate read/write ranges - sc_assert(tmp.is_read_write_allowed()); - mDMIData = tmp; + trans.set_write(); + dmi_type tmp; + if (socket->get_direct_mem_ptr(trans, + tmp)) + { + // FIXME: No support for separate read/write ranges + sc_assert(tmp.is_read_write_allowed()); + mDMIData = tmp; + } } } } } - delete tmp_ext; wait(); - } sync_enum_type myNBTransport(transaction_type& trans, From c2dd01251ced5efd8269ec57b5fa3267aed4dd16 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 12 May 2026 12:51:58 +0200 Subject: [PATCH 38/50] Make sc_simcontext thread_local; handle sub-simcontext construction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/sysc/kernel/sc_process.cpp | 5 ++++- src/sysc/kernel/sc_process.h | 2 +- src/sysc/kernel/sc_process_handle.h | 6 ++++-- src/sysc/kernel/sc_simcontext.cpp | 18 +++++++++++++----- src/sysc/kernel/sc_simcontext.h | 6 ++++-- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/sysc/kernel/sc_process.cpp b/src/sysc/kernel/sc_process.cpp index 54d2c646d..75cc63a1c 100644 --- a/src/sysc/kernel/sc_process.cpp +++ b/src/sysc/kernel/sc_process.cpp @@ -47,13 +47,16 @@ namespace sc_core { // Note the special name for 'non_event' - this makes sure it does not // appear as a named event. +// These two are immutable empty fallbacks returned by sc_process_handle:: +// get_child_events() / get_child_objects() when the handle is empty. +// Read-only after default construction, so no thread_local is needed. std::vector sc_process_handle::empty_event_vector; std::vector sc_process_handle::empty_object_vector; sc_event& sc_process_handle::non_event() { return sc_get_curr_simcontext()->null_event(); } // Last process that was created: -sc_process_b* sc_process_b::m_last_created_process_p = 0; +thread_local sc_process_b* sc_process_b::m_last_created_process_p = 0; //------------------------------------------------------------------------------ //"sc_process_b::add_static_event" diff --git a/src/sysc/kernel/sc_process.h b/src/sysc/kernel/sc_process.h index 87165d2ff..b9be752a2 100644 --- a/src/sysc/kernel/sc_process.h +++ b/src/sysc/kernel/sc_process.h @@ -401,7 +401,7 @@ class SC_API sc_process_b : public sc_object_host { // requesting global suspension. protected: - static sc_process_b* m_last_created_process_p; // Last process created. + thread_local static sc_process_b* m_last_created_process_p; // Last process created. }; diff --git a/src/sysc/kernel/sc_process_handle.h b/src/sysc/kernel/sc_process_handle.h index 4b5657c5c..b9fa2d84f 100644 --- a/src/sysc/kernel/sc_process_handle.h +++ b/src/sysc/kernel/sc_process_handle.h @@ -147,8 +147,10 @@ class SC_API sc_process_handle { sc_process_b* m_target_p; // Target for this object instance. protected: - static std::vector empty_event_vector; // If m_target_p == 0. - static std::vector empty_object_vector; // If m_target_p == 0. + // Immutable empty fallbacks returned when m_target_p == 0. + // Read-only after default construction, so no thread_local needed. + static std::vector empty_event_vector; + static std::vector empty_object_vector; static sc_event& non_event(); // If m_target_p == 0. }; diff --git a/src/sysc/kernel/sc_simcontext.cpp b/src/sysc/kernel/sc_simcontext.cpp index 0dc24d074..eff5f1d11 100644 --- a/src/sysc/kernel/sc_simcontext.cpp +++ b/src/sysc/kernel/sc_simcontext.cpp @@ -307,14 +307,22 @@ sc_simcontext::init() // ALLOCATE VARIOUS MANAGERS AND REGISTRIES: - m_object_manager = new sc_object_manager; + if (sc_curr_simcontext && this!=sc_curr_simcontext) { // Requesting a 'parallel' simcontext + m_object_manager = sc_curr_simcontext->get_object_manager(); // share with parent + m_name_gen = sc_curr_simcontext->m_name_gen; + dynamic_log_verbosity = sc_curr_simcontext->dynamic_log_verbosity; + m_parent_context = sc_curr_simcontext; + } else { + m_object_manager = new sc_object_manager; + m_name_gen = new sc_name_gen; + m_parent_context=nullptr; + } m_module_registry = new sc_module_registry( *this ); m_port_registry = new sc_port_registry( *this ); m_export_registry = new sc_export_registry( *this ); m_prim_channel_registry = new sc_prim_channel_registry( *this ); m_stage_cb_registry = new sc_stage_callback_registry( *this ); m_stub_registry = new sc_stub_registry( *this ); - m_name_gen = new sc_name_gen; m_process_table = new sc_process_table; m_current_writer = 0; @@ -663,7 +671,7 @@ sc_simcontext::elaborate() // (not added to public object hierarchy) m_method_invoker_p = - new sc_invoke_method("$$$$kernel_module$$$$_invoke_method" ); + new sc_invoke_method(("$$$$kernel_module$$$$_invoke_method$" + std::to_string((uint64_t)((void*)this))).c_str()); set_simulation_status(SC_BEFORE_END_OF_ELABORATION); for( int cd = 0; cd != 4; /* empty */ ) @@ -1555,8 +1563,8 @@ void sc_simcontext::post_suspend() const static sc_simcontext sc_default_global_context; sc_simcontext* sc_curr_simcontext = &sc_default_global_context; #else - SC_API sc_simcontext* sc_curr_simcontext = 0; - SC_API sc_simcontext* sc_default_global_context = 0; + thread_local SC_API sc_simcontext* sc_curr_simcontext = 0; + thread_local SC_API sc_simcontext* sc_default_global_context = 0; #endif #else // Not MT-safe! diff --git a/src/sysc/kernel/sc_simcontext.h b/src/sysc/kernel/sc_simcontext.h index 52203fa4e..63efa5003 100644 --- a/src/sysc/kernel/sc_simcontext.h +++ b/src/sysc/kernel/sc_simcontext.h @@ -486,6 +486,8 @@ class SC_API sc_simcontext int m_suspend; int m_unsuspendable; + sc_simcontext* m_parent_context; + private: // disabled @@ -498,8 +500,8 @@ class SC_API sc_simcontext // Not MT safe. #if 1 -extern SC_API sc_simcontext* sc_curr_simcontext; -extern SC_API sc_simcontext* sc_default_global_context; +extern thread_local SC_API sc_simcontext* sc_curr_simcontext; +extern thread_local SC_API sc_simcontext* sc_default_global_context; inline sc_simcontext* sc_get_curr_simcontext() From 792e8b51753330648b71a7f6b1d9b168f589be6f Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 12 May 2026 14:06:24 +0200 Subject: [PATCH 39/50] Thread-safe kernel statics and kernel prim_channel construction 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 (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 --- src/sysc/communication/sc_prim_channel.cpp | 8 +++++--- src/sysc/kernel/sc_event.cpp | 2 +- src/sysc/kernel/sc_name_gen.cpp | 2 ++ src/sysc/kernel/sc_name_gen.h | 3 ++- src/sysc/kernel/sc_object_manager.cpp | 16 +++++++++++++++- src/sysc/kernel/sc_object_manager.h | 2 ++ src/sysc/kernel/sc_simcontext.cpp | 2 +- src/sysc/utils/sc_mempool.cpp | 10 +++++----- 8 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/sysc/communication/sc_prim_channel.cpp b/src/sysc/communication/sc_prim_channel.cpp index 88afac4b6..8bef64ff5 100644 --- a/src/sysc/communication/sc_prim_channel.cpp +++ b/src/sysc/communication/sc_prim_channel.cpp @@ -63,7 +63,6 @@ sc_prim_channel::sc_prim_channel( const char* name_ ) m_registry->insert( *this ); } - // destructor sc_prim_channel::~sc_prim_channel() @@ -194,6 +193,11 @@ class sc_prim_channel_registry::async_update_list m_pop_queue.clear(); } + // attach/detach_suspending and suspend() all touch m_suspending_channels + // and m_has_suspending_channels. By construction every caller is on the + // owning simcontext's thread (sync_window's step_helper and ctor, and + // sc_simcontext's main loop calling async_suspend), so no lock is + // needed. Do NOT call these from a foreign thread. void attach_suspending( sc_prim_channel& p ) { std::vector::iterator it = @@ -202,7 +206,6 @@ class sc_prim_channel_registry::async_update_list m_suspending_channels.push_back(&p); m_has_suspending_channels = true; } - // return releases the mutex } void detach_suspending( sc_prim_channel& p ) @@ -214,7 +217,6 @@ class sc_prim_channel_registry::async_update_list m_suspending_channels.pop_back(); m_has_suspending_channels = (m_suspending_channels.size() > 0); } - // return releases the mutex } async_update_list() : m_has_suspending_channels() {} diff --git a/src/sysc/kernel/sc_event.cpp b/src/sysc/kernel/sc_event.cpp index 74b6cae3f..d7ee67eb9 100644 --- a/src/sysc/kernel/sc_event.cpp +++ b/src/sysc/kernel/sc_event.cpp @@ -540,7 +540,7 @@ union sc_event_timed_u char dummy[sizeof( sc_event_timed )]; }; -static +thread_local static sc_event_timed_u* free_list = 0; void* diff --git a/src/sysc/kernel/sc_name_gen.cpp b/src/sysc/kernel/sc_name_gen.cpp index 72380bad3..b479b4ef8 100644 --- a/src/sysc/kernel/sc_name_gen.cpp +++ b/src/sysc/kernel/sc_name_gen.cpp @@ -45,6 +45,7 @@ sc_name_gen::sc_name_gen() : m_unique_name_map(), m_unique_name() sc_name_gen::~sc_name_gen() { + std::unique_lock lock(m_mutex); sc_strhash::iterator it( m_unique_name_map ); for( ; ! it.empty(); it ++ ) { delete it.contents(); @@ -58,6 +59,7 @@ sc_name_gen::~sc_name_gen() const char* sc_name_gen::gen_unique_name( const char* basename_, bool preserve_first ) { + std::unique_lock lock(m_mutex); if( basename_ == 0 || *basename_ == 0 ) { SC_REPORT_ERROR( SC_ID_GEN_UNIQUE_NAME_, 0 ); basename_ = "unnamed"; // usually not reached diff --git a/src/sysc/kernel/sc_name_gen.h b/src/sysc/kernel/sc_name_gen.h index 2e5c7622a..8928a0781 100644 --- a/src/sysc/kernel/sc_name_gen.h +++ b/src/sysc/kernel/sc_name_gen.h @@ -30,6 +30,7 @@ #ifndef SC_NAME_GEN #define SC_NAME_GEN +#include #include #include "sysc/utils/sc_hash.h" @@ -52,7 +53,7 @@ class sc_name_gen bool preserve_first = false ); private: - + std::mutex m_mutex; sc_strhash m_unique_name_map; std::string m_unique_name; diff --git a/src/sysc/kernel/sc_object_manager.cpp b/src/sysc/kernel/sc_object_manager.cpp index abcb69b36..b7f22d723 100644 --- a/src/sysc/kernel/sc_object_manager.cpp +++ b/src/sysc/kernel/sc_object_manager.cpp @@ -98,7 +98,9 @@ sc_object_manager::~sc_object_manager() // | Result is an std::string containing the name. // +---------------------------------------------------------------------------- std::string sc_object_manager::create_name(const char* leaf_name) -{ +{ + std::unique_lock lock(m_mutex); + bool clash; // true if path name exists in obj table std::string leafname_string; // string containing the leaf name. std::string parentname_string; // parent path name @@ -337,6 +339,8 @@ sc_object_manager::hierarchy_size() bool sc_object_manager::insert_external_name(const std::string& name) { + std::unique_lock lock(m_mutex); + if(!name_exists(name)) { m_instance_table[name].m_element_p = NULL; m_instance_table[name].m_name_origin = SC_NAME_EXTERNAL; @@ -368,6 +372,8 @@ sc_object_manager::insert_external_name(const std::string& name) void sc_object_manager::insert_event(const std::string& name, sc_event* event_p) { + std::unique_lock lock(m_mutex); + m_instance_table[name].m_element_p = static_cast(event_p); m_instance_table[name].m_name_origin = SC_NAME_EVENT; } @@ -385,6 +391,8 @@ sc_object_manager::insert_event(const std::string& name, sc_event* event_p) void sc_object_manager::insert_object(const std::string& name, sc_object* object_p) { + std::unique_lock lock(m_mutex); + m_instance_table[name].m_element_p = static_cast(object_p); m_instance_table[name].m_name_origin = SC_NAME_OBJECT; } @@ -479,6 +487,8 @@ sc_object_manager::top_of_module_name_stack_name() const void sc_object_manager::remove_event(const std::string& name) { + std::unique_lock lock(m_mutex); + instance_table_t::iterator it; // instance table iterator. it = m_instance_table.find(name); if(it != m_instance_table.end() @@ -501,6 +511,8 @@ sc_object_manager::remove_event(const std::string& name) void sc_object_manager::remove_object(const std::string& name) { + std::unique_lock lock(m_mutex); + instance_table_t::iterator it; // instance table iterator. it = m_instance_table.find(name); if(it != m_instance_table.end() @@ -523,6 +535,8 @@ sc_object_manager::remove_object(const std::string& name) bool sc_object_manager::remove_external_name(const std::string& name) { + std::unique_lock lock(m_mutex); + instance_table_t::iterator it; // instance table iterator. it = m_instance_table.find(name); if(it != m_instance_table.end() diff --git a/src/sysc/kernel/sc_object_manager.h b/src/sysc/kernel/sc_object_manager.h index fabc38d42..eccad562e 100644 --- a/src/sysc/kernel/sc_object_manager.h +++ b/src/sysc/kernel/sc_object_manager.h @@ -31,6 +31,7 @@ #define SC_OBJECT_MANAGER_H #include +#include #include namespace sc_core { @@ -110,6 +111,7 @@ class sc_object_manager bool m_event_walk_ok; // true if can walk events. instance_table_t m_instance_table; // table of instances. sc_module_name* m_module_name_stack; // sc_module_name stack. + std::mutex m_mutex; // Mutex to ensure thread safety. instance_table_t::iterator m_object_it; // object instance iterator. object_vector_t m_object_stack; // sc_object stack. bool m_object_walk_ok; // true if can walk objects. diff --git a/src/sysc/kernel/sc_simcontext.cpp b/src/sysc/kernel/sc_simcontext.cpp index eff5f1d11..e799c93c0 100644 --- a/src/sysc/kernel/sc_simcontext.cpp +++ b/src/sysc/kernel/sc_simcontext.cpp @@ -1705,7 +1705,7 @@ sc_start( const sc_time& duration, sc_starvation_policy p ) exit_time = context_p->m_curr_time + duration; // called with duration = SC_ZERO_TIME for the first time - static bool init_delta_or_pending_updates = + thread_local static bool init_delta_or_pending_updates = ( starting_delta == 0 && exit_time == SC_ZERO_TIME ); // If the simulation status is bad issue the appropriate message: diff --git a/src/sysc/utils/sc_mempool.cpp b/src/sysc/utils/sc_mempool.cpp index dc0442d1d..535fd0b24 100644 --- a/src/sysc/utils/sc_mempool.cpp +++ b/src/sysc/utils/sc_mempool.cpp @@ -43,15 +43,15 @@ // set the environment variable SYSTEMC_MEMPOOL_DONT_USE to 1. -static const char* dont_use_envstring = "SYSTEMC_MEMPOOL_DONT_USE"; -static bool use_default_new = false; - - +#include #include #include #include "sysc/kernel/sc_cmnhdr.h" #include "sysc/utils/sc_mempool.h" +static const char* dont_use_envstring = "SYSTEMC_MEMPOOL_DONT_USE"; +static std::atomic use_default_new{false}; + using std::printf; namespace sc_core { @@ -244,7 +244,7 @@ sc_mempool_int::~sc_mempool_int() delete[] allocators; } -static sc_mempool_int* the_mempool = 0; +thread_local static sc_mempool_int* the_mempool = 0; void* sc_mempool_int::do_allocate(std::size_t sz) From d892137ee09d3b18ae482d109858aca760ec7230 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 12 May 2026 13:45:07 +0200 Subject: [PATCH 40/50] Add kernel async runnable helper and run_update_async primitive 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) 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 --- src/sysc/communication/sc_prim_channel.h | 15 ++++ src/sysc/kernel/sc_process.h | 1 + src/sysc/kernel/sc_simcontext.cpp | 50 ++++++++++++- src/sysc/kernel/sc_simcontext.h | 17 +++++ src/sysc/kernel/sc_simcontext_int.h | 90 ++++++++++++++++++++++++ 5 files changed, 171 insertions(+), 2 deletions(-) diff --git a/src/sysc/communication/sc_prim_channel.h b/src/sysc/communication/sc_prim_channel.h index 917563709..6cafb2919 100644 --- a/src/sysc/communication/sc_prim_channel.h +++ b/src/sysc/communication/sc_prim_channel.h @@ -91,6 +91,14 @@ class SC_API sc_prim_channel void async_attach_suspending(); void async_detach_suspending(); + // Run fn() on this channel's owning simcontext's thread. If the caller + // is already on that thread, fn runs synchronously; otherwise the call + // is posted via the kernel's async runnable helper and returns + // immediately, with the callback firing in the owning sim's next + // update phase. Forwards to sc_simcontext::run_update_async, which is + // accessible here because sc_prim_channel is a friend of sc_simcontext. + inline void run_update_async( std::function fn ); + protected: // to avoid calling sc_get_curr_simcontext() @@ -361,6 +369,13 @@ sc_prim_channel::async_detach_suspending() m_registry->async_detach_suspending(*this); } +inline +void +sc_prim_channel::run_update_async( std::function fn ) +{ + simcontext()->run_update_async( std::move(fn) ); +} + // called during the update phase of a delta cycle (if requested) diff --git a/src/sysc/kernel/sc_process.h b/src/sysc/kernel/sc_process.h index b9be752a2..0d3decbb2 100644 --- a/src/sysc/kernel/sc_process.h +++ b/src/sysc/kernel/sc_process.h @@ -214,6 +214,7 @@ class SC_API sc_process_b : public sc_object_host { friend class sc_object; friend class sc_port_base; friend class sc_runnable; + friend class sc_async_runnable_helper; friend class sc_sensitive; friend class sc_sensitive_pos; friend class sc_sensitive_neg; diff --git a/src/sysc/kernel/sc_simcontext.cpp b/src/sysc/kernel/sc_simcontext.cpp index e799c93c0..2394e33c0 100644 --- a/src/sysc/kernel/sc_simcontext.cpp +++ b/src/sysc/kernel/sc_simcontext.cpp @@ -295,6 +295,38 @@ class sc_invoke_method : public sc_module std::vector m_invokers; // list of invoking threads. }; +// ---------------------------------------------------------------------------- +// CLASS : sc_async_runnable_helper +// +// Drain queued foreign runnable-push requests onto the local runnable +// lists during this simcontext's update phase. +// ---------------------------------------------------------------------------- + +void +sc_async_runnable_helper::update() +{ + std::vector ms; + std::vector ts; + std::vector> cbs; + { + std::lock_guard lg( m_mutex ); + ms.swap( m_pending_methods ); + ts.swap( m_pending_threads ); + cbs.swap( m_pending_callbacks ); + } + // Callbacks first: they may themselves enqueue runnables that we + // then process locally below. + for ( auto& cb : cbs ) + cb(); + sc_simcontext* simc = simcontext(); + for ( auto* m : ms ) + if ( !m->is_runnable() ) + simc->push_runnable_method( m ); + for ( auto* t : ts ) + if ( !t->is_runnable() ) + simc->push_runnable_thread( t ); +} + // ---------------------------------------------------------------------------- // CLASS : sc_simcontext // @@ -326,6 +358,16 @@ sc_simcontext::init() m_process_table = new sc_process_table; m_current_writer = 0; + // Construct the async runnable helper in *this* simcontext's registry. + // sc_prim_channel's base sc_object captures sc_curr_simcontext, which + // during a child sim's init() still points at the parent — so swap. + { + sc_simcontext* saved = sc_curr_simcontext; + sc_curr_simcontext = this; + m_async_runnable_helper = new sc_async_runnable_helper(); + sc_curr_simcontext = saved; + } + // CHECK FOR ENVIRONMENT VARIABLES THAT MODIFY SIMULATOR EXECUTION: @@ -374,11 +416,13 @@ sc_simcontext::init() void sc_simcontext::clean() { + if (m_parent_context) return; // assume the parent will delete us // remove remaining zombie processes do_collect_processes(); delete m_stub_registry; delete m_method_invoker_p; + delete m_async_runnable_helper; // must precede m_prim_channel_registry delete m_error; delete m_cor_pkg; delete m_time_params; @@ -387,13 +431,15 @@ sc_simcontext::clean() delete m_null_event_p; delete m_timed_events; delete m_process_table; - delete m_name_gen; delete m_stage_cb_registry; delete m_prim_channel_registry; delete m_export_registry; delete m_port_registry; delete m_module_registry; - delete m_object_manager; + if (!m_parent_context) { + delete m_name_gen; + delete m_object_manager; + } m_delta_events.clear(); m_child_objects.clear(); diff --git a/src/sysc/kernel/sc_simcontext.h b/src/sysc/kernel/sc_simcontext.h index 63efa5003..c8fd1194f 100644 --- a/src/sysc/kernel/sc_simcontext.h +++ b/src/sysc/kernel/sc_simcontext.h @@ -30,6 +30,8 @@ #ifndef SC_SIMCONTEXT_H #define SC_SIMCONTEXT_H +#include + #include "sysc/kernel/sc_cmnhdr.h" #include "sysc/kernel/sc_process.h" #include "sysc/kernel/sc_status.h" @@ -79,6 +81,7 @@ class sc_cthread_process; class sc_thread_process; class sc_reset_finder; class sc_stub_registry; +class sc_async_runnable_helper; extern sc_simcontext* sc_get_curr_simcontext(); @@ -197,6 +200,7 @@ class SC_API sc_simcontext friend class sc_time_tuple; friend class sc_clock; friend class sc_method_process; + friend class sc_async_runnable_helper; friend class sc_stage_callback_registry; friend class sc_port_registry; friend class sc_process_b; @@ -396,6 +400,18 @@ class SC_API sc_simcontext void push_runnable_method( sc_method_handle ); void push_runnable_thread( sc_thread_handle ); + // Cross-simcontext-safe variants: if the caller's sc_curr_simcontext + // differs from this, route via the async runnable helper so the + // runnable list is only ever mutated on its owning thread. + void push_runnable_method_async( sc_method_handle ); + void push_runnable_thread_async( sc_thread_handle ); + + // Run fn() on this simcontext's thread. If foreign, posts via the + // async helper and returns immediately; the callback fires in the + // owning sim's next update phase. If local, runs synchronously. + // Kernel-internal; channels reach it via sc_prim_channel's forwarder. + void run_update_async( std::function fn ); + void push_runnable_method_front( sc_method_handle ); void push_runnable_thread_front( sc_thread_handle ); @@ -462,6 +478,7 @@ class SC_API sc_simcontext sc_time m_curr_time; sc_invoke_method* m_method_invoker_p; + sc_async_runnable_helper* m_async_runnable_helper; sc_dt::uint64 m_change_stamp; // "time" change occurred. sc_dt::uint64 m_delta_count; sc_dt::uint64 m_initial_delta_count_at_current_time; diff --git a/src/sysc/kernel/sc_simcontext_int.h b/src/sysc/kernel/sc_simcontext_int.h index 54bda9556..28291dbb2 100644 --- a/src/sysc/kernel/sc_simcontext_int.h +++ b/src/sysc/kernel/sc_simcontext_int.h @@ -32,6 +32,11 @@ #ifndef SC_SIMCONTEXT_INT_H #define SC_SIMCONTEXT_INT_H +#include +#include +#include + +#include "sysc/communication/sc_prim_channel.h" #include "sysc/kernel/sc_simcontext.h" #include "sysc/kernel/sc_runnable.h" #include "sysc/kernel/sc_runnable_int.h" @@ -61,6 +66,57 @@ namespace sc_core { +// ---------------------------------------------------------------------------- +// CLASS : sc_async_runnable_helper +// +// Kernel prim_channel owned by each sc_simcontext that accepts +// cross-simcontext work requests and drains them during the owning +// simcontext's update phase. Three entry points: +// post_method / post_thread - fast path, used by trigger fan-out to +// enqueue a single runnable for the foreign sim's next delta. +// post_callback - general path for whole-function calls +// that must happen on the owning sim's thread (e.g. enable/resume +// which read-modify-write m_state). +// Intentionally private to kernel (internal header). +// ---------------------------------------------------------------------------- + +class sc_async_runnable_helper : public sc_prim_channel +{ +public: + sc_async_runnable_helper() : sc_prim_channel() {} + + void post_method( sc_method_process* m ) + { + std::lock_guard lg( m_mutex ); + m_pending_methods.push_back( m ); + async_request_update(); + } + + void post_thread( sc_thread_process* t ) + { + std::lock_guard lg( m_mutex ); + m_pending_threads.push_back( t ); + async_request_update(); + } + + void post_callback( std::function fn ) + { + std::lock_guard lg( m_mutex ); + m_pending_callbacks.push_back( std::move( fn ) ); + async_request_update(); + } + +protected: + // defined in sc_simcontext.cpp (needs full sc_method_process etc. definitions) + virtual void update(); + +private: + std::mutex m_mutex; + std::vector m_pending_methods; + std::vector m_pending_threads; + std::vector> m_pending_callbacks; +}; + // We use m_current_writer rather than m_curr_proc_info.process_handle to // return the active process for sc_signal::check_write since that lets // us turn it off a library compile time, and only incur the overhead at @@ -205,6 +261,40 @@ sc_simcontext::push_runnable_method( sc_method_handle method_h ) m_runnable->push_back_method( method_h ); } +inline +void +sc_simcontext::push_runnable_method_async( sc_method_handle method_h ) +{ + if ( this != sc_get_curr_simcontext() ) + m_async_runnable_helper->post_method( method_h ); + else + push_runnable_method( method_h ); +} + +inline +void +sc_simcontext::push_runnable_thread_async( sc_thread_handle thread_h ) +{ + if ( this != sc_get_curr_simcontext() ) + m_async_runnable_helper->post_thread( thread_h ); + else + push_runnable_thread( thread_h ); +} + +// Run fn() on this simcontext's thread. If we're already on it, run +// synchronously; otherwise post via the async helper to drain in the +// owning sim's next update phase. Used to safely route whole-function +// calls (e.g. enable/resume) that read-modify-write process state. +inline +void +sc_simcontext::run_update_async( std::function fn ) +{ + if ( this != sc_get_curr_simcontext() ) + m_async_runnable_helper->post_callback( std::move( fn ) ); + else + fn(); +} + inline void sc_simcontext::push_runnable_method_front( sc_method_handle method_h ) From 80aedead436a873d25188215046557b1689a349b Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 12 May 2026 14:14:46 +0200 Subject: [PATCH 41/50] Make sc_event notify/cancel cross-simcontext safe 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 --- src/sysc/kernel/sc_event.cpp | 22 ++++++++++++++++++++++ src/sysc/kernel/sc_event.h | 8 ++++++++ 2 files changed, 30 insertions(+) diff --git a/src/sysc/kernel/sc_event.cpp b/src/sysc/kernel/sc_event.cpp index d7ee67eb9..a6135a307 100644 --- a/src/sysc/kernel/sc_event.cpp +++ b/src/sysc/kernel/sc_event.cpp @@ -30,6 +30,7 @@ #include #include "sysc/kernel/sc_event.h" +#include "sc_simcontext.h" #include "sysc/kernel/sc_kernel_ids.h" #include "sysc/kernel/sc_stage_callback_registry.h" #include "sysc/kernel/sc_process.h" @@ -61,6 +62,10 @@ sc_event::basename() const void sc_event::cancel() { + if (m_simc != sc_get_curr_simcontext()) { + m_simc->run_update_async( [this]{ cancel(); } ); + return; + } // cancel a delta or timed notification switch( m_notify_type ) { case DELTA: { @@ -86,6 +91,10 @@ sc_event::cancel() void sc_event::notify() { + if (m_simc != sc_get_curr_simcontext()) { + m_simc->run_update_async( [this]{ notify(); } ); + return; + } // immediate notification if( !m_simc->evaluation_phase() ) // coming from @@ -103,6 +112,10 @@ sc_event::notify() void sc_event::notify( const sc_time& t ) { + if (m_simc != sc_get_curr_simcontext()) { + m_simc->run_update_async( [this, t]{ notify(t); } ); + return; + } if( m_notify_type == DELTA ) { return; } @@ -168,6 +181,10 @@ static void sc_warn_notify_delayed() void sc_event::notify_delayed() { + if (m_simc != sc_get_curr_simcontext()) { + m_simc->run_update_async( [this]{ notify_delayed(); } ); + return; + } sc_warn_notify_delayed(); if( m_notify_type != NONE ) { SC_REPORT_ERROR( SC_ID_NOTIFY_DELAYED_, 0 ); @@ -180,6 +197,11 @@ sc_event::notify_delayed() void sc_event::notify_delayed( const sc_time& t ) { + if (m_simc != sc_get_curr_simcontext()) { + m_simc->run_update_async( [this, t]{ notify_delayed(t); } ); + return; + } + sc_warn_notify_delayed(); if( m_notify_type != NONE ) { SC_REPORT_ERROR( SC_ID_NOTIFY_DELAYED_, 0 ); diff --git a/src/sysc/kernel/sc_event.h b/src/sysc/kernel/sc_event.h index d7884f6b3..2852702c9 100644 --- a/src/sysc/kernel/sc_event.h +++ b/src/sysc/kernel/sc_event.h @@ -424,6 +424,10 @@ inline void sc_event::notify_internal( const sc_time& t ) { + if (m_simc != sc_get_curr_simcontext()) { + m_simc->run_update_async( [this, t]{ notify_internal(t); } ); + return; + } if( t == SC_ZERO_TIME ) { // add this event to the delta events set m_delta_event_index = m_simc->add_delta_event( this ); @@ -441,6 +445,10 @@ inline void sc_event::notify_next_delta() { + if (m_simc != sc_get_curr_simcontext()) { + m_simc->run_update_async( [this]{ notify_next_delta(); } ); + return; + } if( m_notify_type != NONE ) { SC_REPORT_ERROR( SC_ID_NOTIFY_DELAYED_, 0 ); } From ff33f4e0a65b6bae26ba185e7341bee87314c4ed Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 7 May 2026 13:50:10 +0200 Subject: [PATCH 42/50] Make process state mutations cross-simcontext safe 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 --- src/sysc/kernel/sc_method_process.cpp | 12 +++++++++++- src/sysc/kernel/sc_method_process.h | 2 +- src/sysc/kernel/sc_thread_process.cpp | 12 +++++++++++- src/sysc/kernel/sc_thread_process.h | 2 +- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/sysc/kernel/sc_method_process.cpp b/src/sysc/kernel/sc_method_process.cpp index 9b868b372..bc257be4a 100644 --- a/src/sysc/kernel/sc_method_process.cpp +++ b/src/sysc/kernel/sc_method_process.cpp @@ -195,6 +195,11 @@ void sc_method_process::disable_process( void sc_method_process::enable_process( sc_descendant_inclusion_info descendants ) { + // m_state read-modify-write must happen on the owning sim's thread. + if ( simcontext() != sc_get_curr_simcontext() ) { + simcontext()->run_update_async( [this, descendants]{ enable_process(descendants); } ); + return; + } // IF NEEDED PROPOGATE THE RESUME REQUEST THROUGH OUR DESCENDANTS: @@ -426,6 +431,11 @@ void sc_method_process::suspend_process( void sc_method_process::resume_process( sc_descendant_inclusion_info descendants ) { + // m_state read-modify-write must happen on the owning sim's thread. + if ( simcontext() != sc_get_curr_simcontext() ) { + simcontext()->run_update_async( [this, descendants]{ resume_process(descendants); } ); + return; + } // IF NEEDED PROPOGATE THE RESUME REQUEST THROUGH OUR DESCENDANTS: @@ -771,7 +781,7 @@ bool sc_method_process::trigger_dynamic( sc_event* e ) } else { - simcontext()->push_runnable_method(this); + simcontext()->push_runnable_method_async(this); } return true; diff --git a/src/sysc/kernel/sc_method_process.h b/src/sysc/kernel/sc_method_process.h index b7b0a200e..b177e898f 100644 --- a/src/sysc/kernel/sc_method_process.h +++ b/src/sysc/kernel/sc_method_process.h @@ -362,7 +362,7 @@ sc_method_process::trigger_static() } else { - simcontext()->push_runnable_method(this); + simcontext()->push_runnable_method_async(this); } } diff --git a/src/sysc/kernel/sc_thread_process.cpp b/src/sysc/kernel/sc_thread_process.cpp index 36991c113..024bd302d 100644 --- a/src/sysc/kernel/sc_thread_process.cpp +++ b/src/sysc/kernel/sc_thread_process.cpp @@ -220,6 +220,11 @@ void sc_thread_process::disable_process( void sc_thread_process::enable_process( sc_descendant_inclusion_info descendants ) { + // m_state read-modify-write must happen on the owning sim's thread. + if ( simcontext() != sc_get_curr_simcontext() ) { + simcontext()->run_update_async( [this, descendants]{ enable_process(descendants); } ); + return; + } // IF NEEDED PROPOGATE THE ENABLE REQUEST THROUGH OUR DESCENDANTS: @@ -338,6 +343,11 @@ void sc_thread_process::prepare_for_simulation() void sc_thread_process::resume_process( sc_descendant_inclusion_info descendants ) { + // m_state read-modify-write must happen on the owning sim's thread. + if ( simcontext() != sc_get_curr_simcontext() ) { + simcontext()->run_update_async( [this, descendants]{ resume_process(descendants); } ); + return; + } // IF NEEDED PROPOGATE THE RESUME REQUEST THROUGH OUR DESCENDANTS: @@ -832,7 +842,7 @@ bool sc_thread_process::trigger_dynamic( sc_event* e ) } else { - simcontext()->push_runnable_thread(this); + simcontext()->push_runnable_thread_async(this); } return true; diff --git a/src/sysc/kernel/sc_thread_process.h b/src/sysc/kernel/sc_thread_process.h index cf566e160..b845b4478 100644 --- a/src/sysc/kernel/sc_thread_process.h +++ b/src/sysc/kernel/sc_thread_process.h @@ -505,7 +505,7 @@ sc_thread_process::trigger_static() return; } - simcontext()->push_runnable_thread(this); + simcontext()->push_runnable_thread_async(this); } #undef DEBUG_MSG From df181230fb9404fffea497d25e40c76992502ee4 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 7 May 2026 13:50:35 +0200 Subject: [PATCH 43/50] Make sc_signal::write cross-simcontext safe 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::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 --- src/sysc/communication/sc_signal.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sysc/communication/sc_signal.h b/src/sysc/communication/sc_signal.h index f1504c345..11edacf52 100644 --- a/src/sysc/communication/sc_signal.h +++ b/src/sysc/communication/sc_signal.h @@ -288,6 +288,18 @@ inline void sc_signal_t::write( const T& value_ ) { + // Cross-simcontext write: m_new_val and request_update both touch + // state that belongs to the signal's owning sim, so route the whole + // write onto that sim's update phase via run_update_async. The + // foreign caller returns immediately; the write lands at the next + // update on the owner thread. Reader-side wake-up is already + // handled by the cross-context fan-out path on the value-changed + // event. + if ( sc_get_curr_simcontext() != this->simcontext() ) { + this->run_update_async( [this, value_]{ this->write(value_); } ); + return; + } + // first write per eval phase: m_new_val == m_cur_val bool value_changed = !( m_new_val == value_ ); if ( !policy_type::check_write(this, value_changed) ) From f8c2aab9d614454088436535ed505228b3b6fb15 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 12 May 2026 14:35:59 +0200 Subject: [PATCH 44/50] Make sc_report_handler state thread_local MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/sysc/utils/sc_report_handler.cpp | 68 ++++++++++++++++++++++------ src/sysc/utils/sc_report_handler.h | 62 +++++++++++++++++++------ 2 files changed, 102 insertions(+), 28 deletions(-) diff --git a/src/sysc/utils/sc_report_handler.cpp b/src/sysc/utils/sc_report_handler.cpp index eb48b58e9..7b2da8241 100644 --- a/src/sysc/utils/sc_report_handler.cpp +++ b/src/sysc/utils/sc_report_handler.cpp @@ -42,7 +42,7 @@ namespace std {} namespace sc_core { -int sc_report_handler::verbosity_level = SC_MEDIUM; +thread_local int sc_report_handler::verbosity_level = SC_MEDIUM; // not documented, but available const std::string sc_report_compose_message(const sc_report& rep) @@ -178,7 +178,7 @@ ::std::ofstream& sc_log_file_handle::operator*() { return log_stream; } -static sc_log_file_handle log_stream; +static thread_local sc_log_file_handle log_stream; // @@ -689,6 +689,48 @@ const char * sc_report_handler::get_log_file_name() return log_file_name; } +// Snapshot of the current thread's report-handler configuration, for +// hand-off to a freshly-spawned thread (sc_concurrent's child simcontext). +sc_report_handler::config_snapshot +sc_report_handler::snapshot_config() +{ + config_snapshot s; + s.handler = handler; + s.verbosity = verbosity_level; + s.suppress = suppress_mask; + s.force = force_mask; + s.catch_ = catch_actions; + s.available = available_actions; + for (int i = 0; i < SC_MAX_SEVERITY; ++i) { + s.actions_per_sev[i] = sev_actions[i]; + s.limit_per_sev[i] = sev_limit[i]; + } + if (log_file_name) s.log_file_name = log_file_name; + return s; +} + +void +sc_report_handler::apply_config(const config_snapshot& s) +{ + handler = s.handler; + verbosity_level = s.verbosity; + suppress_mask = s.suppress; + force_mask = s.force; + catch_actions = s.catch_; + available_actions = s.available; + for (int i = 0; i < SC_MAX_SEVERITY; ++i) { + sev_actions[i] = s.actions_per_sev[i]; + sev_limit[i] = s.limit_per_sev[i]; + } + if (!s.log_file_name.empty()) { + // set_log_file_name allocates via malloc. Free any previous + // (default thread_local) value first to avoid the "already set" + // refusal in set_log_file_name. + if (log_file_name) { free(log_file_name); log_file_name = 0; } + set_log_file_name(s.log_file_name.c_str()); + } +} + void sc_report_handler::cache_report(const sc_report& rep) { sc_process_b * proc = sc_get_current_process_b(); @@ -730,10 +772,10 @@ int sc_report_handler::set_verbosity_level( int level ) // static variables // -sc_actions sc_report_handler::suppress_mask = 0; -sc_actions sc_report_handler::force_mask = 0; +thread_local sc_actions sc_report_handler::suppress_mask = 0; +thread_local sc_actions sc_report_handler::force_mask = 0; -sc_actions sc_report_handler::sev_actions[SC_MAX_SEVERITY] = +thread_local sc_actions sc_report_handler::sev_actions[SC_MAX_SEVERITY] = { /* info */ SC_DEFAULT_INFO_ACTIONS, /* warn */ SC_DEFAULT_WARNING_ACTIONS, @@ -743,14 +785,14 @@ sc_actions sc_report_handler::sev_actions[SC_MAX_SEVERITY] = // Note that SC_FATAL has a limit of 1 by default -sc_actions sc_report_handler::sev_limit[SC_MAX_SEVERITY] = +thread_local sc_actions sc_report_handler::sev_limit[SC_MAX_SEVERITY] = { UINT_MAX, UINT_MAX, UINT_MAX, UINT_MAX }; -sc_actions sc_report_handler::sev_call_count[SC_MAX_SEVERITY] = { 0, 0, 0, 0 }; +thread_local sc_actions sc_report_handler::sev_call_count[SC_MAX_SEVERITY] = { 0, 0, 0, 0 }; -sc_report* sc_report_handler::last_global_report = NULL; -sc_actions sc_report_handler::available_actions = +thread_local sc_report* sc_report_handler::last_global_report = NULL; +thread_local sc_actions sc_report_handler::available_actions = SC_DO_NOTHING | SC_THROW | SC_LOG | @@ -760,16 +802,16 @@ sc_actions sc_report_handler::available_actions = SC_STOP | SC_ABORT; -sc_report_handler_proc sc_report_handler::handler = +thread_local sc_report_handler_proc sc_report_handler::handler = &sc_report_handler::default_handler; -char * sc_report_handler::log_file_name = 0; +thread_local char * sc_report_handler::log_file_name = 0; -sc_report_handler::msg_def_items * sc_report_handler::messages = +thread_local sc_report_handler::msg_def_items * sc_report_handler::messages = &sc_report_handler::msg_terminator; -sc_actions sc_report_handler::catch_actions = SC_DEFAULT_CATCH_ACTIONS; +thread_local sc_actions sc_report_handler::catch_actions = SC_DEFAULT_CATCH_ACTIONS; sc_actions sc_report_handler::set_catch_actions(sc_actions act) { diff --git a/src/sysc/utils/sc_report_handler.h b/src/sysc/utils/sc_report_handler.h index 541aea33f..a7756e24e 100644 --- a/src/sysc/utils/sc_report_handler.h +++ b/src/sysc/utils/sc_report_handler.h @@ -30,6 +30,8 @@ #ifndef SC_REPORT_HANDLER_H #define SC_REPORT_HANDLER_H +#include + namespace sc_core { // ---------------------------------------------------------------------------- @@ -118,6 +120,26 @@ class SC_API sc_report_handler static bool set_log_file_name(const char* filename); static const char* get_log_file_name(); + // Snapshot of this thread's report-handler configuration, used to + // inherit settings onto a freshly-spawned thread (sc_concurrent's + // child simcontext). Call snapshot_config() on the parent thread + // before the child starts; call apply_config() first thing on the + // child thread. Counters (sev_call_count) are deliberately NOT + // in the snapshot — a new thread starts with fresh counters. + struct config_snapshot { + sc_report_handler_proc handler; + int verbosity; + sc_actions suppress; + sc_actions force; + sc_actions catch_; + sc_actions available; + sc_actions actions_per_sev[SC_MAX_SEVERITY]; + unsigned limit_per_sev[SC_MAX_SEVERITY]; + std::string log_file_name; // empty == none + }; + static config_snapshot snapshot_config(); + static void apply_config(const config_snapshot&); + // how the implementation should handle caught (sc_report) exceptions static sc_actions set_catch_actions(sc_actions); static sc_actions get_catch_actions(); @@ -140,21 +162,31 @@ class SC_API sc_report_handler static void cache_report(const sc_report&); static sc_actions execute(sc_msg_def*, sc_severity); - static sc_actions suppress_mask; - static sc_actions force_mask; - static sc_actions sev_actions[SC_MAX_SEVERITY]; - static unsigned sev_limit[SC_MAX_SEVERITY]; - static unsigned sev_call_count[SC_MAX_SEVERITY]; - static sc_report* last_global_report; - static sc_actions available_actions; - static sc_actions catch_actions; - static char* log_file_name; - static int verbosity_level; - - static msg_def_items* messages; - static msg_def_items msg_terminator; - - static sc_report_handler_proc handler; + // Report-handler state is thread_local so that concurrent calls from + // parallel simcontexts do not race on counters, masks, or the handler + // pointer. Consequence: set_handler / set_verbosity_level / set_actions + // only affect the thread they are called from. A user that wants + // cross-thread policy (unified counters, ordered log output, one handler + // everywhere) should install a thread-safe handler from each thread; + // the back-end is where that coordination belongs. + // + // msg_terminator is read-only shared (compile-time initialised, never + // written), so it does NOT need to be thread_local. + static thread_local sc_actions suppress_mask; + static thread_local sc_actions force_mask; + static thread_local sc_actions sev_actions[SC_MAX_SEVERITY]; + static thread_local unsigned sev_limit[SC_MAX_SEVERITY]; + static thread_local unsigned sev_call_count[SC_MAX_SEVERITY]; + static thread_local sc_report* last_global_report; + static thread_local sc_actions available_actions; + static thread_local sc_actions catch_actions; + static thread_local char* log_file_name; + static thread_local int verbosity_level; + + static thread_local msg_def_items* messages; + static msg_def_items msg_terminator; + + static thread_local sc_report_handler_proc handler; static sc_msg_def* mdlookup(const char* msg_type); From 764848c4ac59fe4b964b32c936b0c23782428bde Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 7 May 2026 13:55:23 +0200 Subject: [PATCH 45/50] Add sc_on_context and route TLM b_transport / nb_transport_fw 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 --- src/sysc/utils/sc_on_context.h | 275 +++++++++++++++++++++++++++ src/tlm_utils/simple_target_socket.h | 21 +- 2 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 src/sysc/utils/sc_on_context.h diff --git a/src/sysc/utils/sc_on_context.h b/src/sysc/utils/sc_on_context.h new file mode 100644 index 000000000..6583ea3b3 --- /dev/null +++ b/src/sysc/utils/sc_on_context.h @@ -0,0 +1,275 @@ +/***************************************************************************** + + 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. + + *****************************************************************************/ + +/***************************************************************************** + + sc_on_context.h -- Job handler to run on context + + Original Author: Mark burton + + CHANGE LOG AT END OF FILE + *****************************************************************************/ + +#ifndef SC_ON_CONTEXT_H +#define SC_ON_CONTEXT_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace sc_core { + +class sc_on_context : public sc_core::sc_module +{ +protected: + class AsyncJob + { + public: + using Ptr = std::shared_ptr; + + private: + std::packaged_task m_task; + + bool m_cancelled = false; + + void run_job() { m_task(); } + + public: + AsyncJob(std::function&& job): m_task(job) {} + + AsyncJob(std::function& job): m_task(job) {} + + AsyncJob() = delete; + AsyncJob(const AsyncJob&) = delete; + + void operator()() { run_job(); } + + /** + * @brief Cancel a job + * + * @details Cancel a job by setting m_cancelled to true and by + * resetting the task. Any waiter will then be unblocked immediately. + */ + void cancel() + { + m_cancelled = true; + m_task.reset(); + } + + void wait() + { + auto future = m_task.get_future(); + + while (!m_cancelled && + future.wait_for(std::chrono::milliseconds(500))==std::future_status::timeout) + {} + + if (!m_cancelled) { + future.get(); + } + } + + bool is_cancelled() const { return m_cancelled; } + }; + + + sc_core::sc_simcontext *m_simc=nullptr; + + /* Async job queue */ + std::queue m_async_jobs; + AsyncJob::Ptr m_running_job; + std::mutex m_async_jobs_mutex; + + sc_event m_jobs_handler_event; // NB this event must be threadsafe. + std::atomic running = true; + + // Process inside a thread incase the job calls wait + void jobs_handler() + { + std::unique_lock lock(m_async_jobs_mutex); + running = true; + for (; running;) { + while (running && !m_async_jobs.empty()) { + m_running_job = m_async_jobs.front(); + m_async_jobs.pop(); + + lock.unlock(); + sc_core::sc_unsuspendable(); // a wait in the job will cause systemc time to advance + (*m_running_job)(); + sc_core::sc_suspendable(); + lock.lock(); + + m_running_job.reset(); + } + lock.unlock(); + wait(m_jobs_handler_event); + lock.lock(); + } + SC_REPORT_WARNING("sc_on_context", "Stopped"); + sc_core::sc_stop(); + } + + void cancel_pendings_locked() + { + while (!m_async_jobs.empty()) { + m_async_jobs.front()->cancel(); + m_async_jobs.pop(); + } + } + +public: + sc_on_context(const sc_core::sc_module_name& n = sc_core::sc_module_name("sc_on_ctx")) + : sc_module(n) + { + m_simc = sc_core::sc_get_curr_simcontext(); + SC_THREAD(jobs_handler); + } + + /** + * @brief Cancel all pending jobs + * + * @detail Cancel all the pending jobs. The callers will be unblocked + * if they are waiting for the job. + */ + void cancel_pendings() + { + std::lock_guard lock(m_async_jobs_mutex); + + cancel_pendings_locked(); + } + + /** + * @brief Cancel all pending and running jobs + * + * @detail Cancel all the pending jobs and the currently running job. + * The callers will be unblocked if they are waiting for the + * job. Note that if the currently running job is resumed, the + * behaviour is undefined. This method is meant to be called + * after simulation has ended. + */ + void cancel_all() + { + std::lock_guard lock(m_async_jobs_mutex); + + cancel_pendings_locked(); + + if (m_running_job) { + m_running_job->cancel(); + m_running_job.reset(); + } + } + void stop() + { + running = false; + m_jobs_handler_event.notify(sc_core::SC_ZERO_TIME); + } + + void end_of_simulation() + { + running = false; + cancel_all(); + } + + void fork(std::function job_entry) { run(job_entry, false); } + + /** + * @brief Run a job on the SystemC kernel thread + * + * @param[in] job_entry The job to run + * @param[in] wait If true, wait for job completion + * + * @return true if the job has been succesfully executed or if `wait` + * was false, false if it has been cancelled (see + * `Sc_on_context::cancel_all`). + */ + bool run(std::function job_entry, bool wait = true) + { + if (!running) return false; + if (on_owning_ctx()) { + job_entry(); + return true; + } else { + AsyncJob::Ptr job(new AsyncJob(job_entry)); + + { + std::lock_guard lock(m_async_jobs_mutex); + if (running) { + m_async_jobs.push(job); + } else { + return false; + } + } + + m_jobs_handler_event.notify(); + + if (wait) { + /* Wait for job completion */ + try { + job->wait(); + } catch (std::runtime_error const& e) { + /* Report unknown runtime errors, without causing a futher excetion */ + auto old = sc_core::sc_report_handler::set_actions(sc_core::SC_ERROR, + sc_core::SC_LOG | sc_core::SC_DISPLAY); + SC_REPORT_ERROR( + "Sc_on_context", + ("Run on systemc received a runtime error from job: " + std::string(e.what())).c_str()); + sc_core::sc_report_handler::set_actions(sc_core::SC_ERROR, old); + stop(); + return false; + } catch (const std::exception& exc) { + if (sc_core::sc_report_handler::get_count(sc_core::SC_ERROR) == 0) { + /* Report exceptions that were not caused by SC_ERRORS (which have already been reported)*/ + auto old = sc_core::sc_report_handler::set_actions(sc_core::SC_ERROR, + sc_core::SC_LOG | sc_core::SC_DISPLAY); + SC_REPORT_ERROR( + "Sc_on_context", + ("Run on systemc received an exception from job: " + std::string(exc.what())).c_str()); + sc_core::sc_report_handler::set_actions(sc_core::SC_ERROR, old); + } + stop(); + return false; + } catch (...) { + auto old = sc_core::sc_report_handler::set_actions(sc_core::SC_ERROR, + sc_core::SC_LOG | sc_core::SC_DISPLAY); + SC_REPORT_ERROR("Sc_on_context", "Run on systemc received an unknown exception from job"); + sc_core::sc_report_handler::set_actions(sc_core::SC_ERROR, old); + stop(); + return false; + } + + return !job->is_cancelled(); + } + + return true; + } + } + + /** + * @return Whether we are on the right SystemC context + */ + bool on_owning_ctx() const { return sc_core::sc_get_curr_simcontext()==m_simc; } +}; +} +#endif // SC_ON_CONTEXT_H \ No newline at end of file diff --git a/src/tlm_utils/simple_target_socket.h b/src/tlm_utils/simple_target_socket.h index 82198fbb6..52b4eedbc 100644 --- a/src/tlm_utils/simple_target_socket.h +++ b/src/tlm_utils/simple_target_socket.h @@ -37,6 +37,7 @@ #include #include "tlm_utils/convenience_socket_bases.h" #include "tlm_utils/peq_with_get.h" +#include "sysc/utils/sc_on_context.h" namespace tlm_utils { @@ -174,6 +175,7 @@ class simple_target_socket_b class fw_process : public tlm::tlm_fw_transport_if, public tlm::tlm_mm_interface { + sc_core::sc_on_context m_on_ctx; public: typedef sync_enum_type (MODULE::*NBTransportPtr)(transaction_type&, phase_type&, @@ -257,7 +259,14 @@ class simple_target_socket_b if (m_nb_transport_ptr) { // forward call sc_assert(m_mod); - return (m_mod->*m_nb_transport_ptr)(trans, phase, t); + if (!m_on_ctx.on_owning_ctx()) + { + sync_enum_type tmp; + m_on_ctx.run([this, &tmp, &trans, &phase, &t](){ tmp=(m_mod->*m_nb_transport_ptr)(trans, phase, t);}); + return tmp; + } else { + return (m_mod->*m_nb_transport_ptr)(trans, phase, t); + } } // nb->b conversion @@ -300,7 +309,15 @@ class simple_target_socket_b if (m_b_transport_ptr) { // forward call sc_assert(m_mod); - (m_mod->*m_b_transport_ptr)(trans, t); + if (!m_on_ctx.on_owning_ctx()) { + sc_core::sc_time our_time=sc_core::sc_time_stamp(); + sc_core::sc_time their_time; + m_on_ctx.run( + [this, &trans, &t, &our_time, &their_time]() { if (our_time>sc_core::sc_time_stamp()) wait(our_time-sc_core::sc_time_stamp()); (m_mod->*m_b_transport_ptr)(trans, t); their_time=sc_core::sc_time_stamp(); }); + if (their_time>sc_core::sc_time_stamp()) wait(their_time-sc_core::sc_time_stamp()); + } else { + (m_mod->*m_b_transport_ptr)(trans, t); + } return; } From 3ea98e5967d251e40af17750051f8495d940d02f Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 7 May 2026 13:55:35 +0200 Subject: [PATCH 46/50] Add sc_ob_event utility 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 --- src/sysc/utils/sc_ob_event.h | 96 ++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/sysc/utils/sc_ob_event.h diff --git a/src/sysc/utils/sc_ob_event.h b/src/sysc/utils/sc_ob_event.h new file mode 100644 index 000000000..75a0c4812 --- /dev/null +++ b/src/sysc/utils/sc_ob_event.h @@ -0,0 +1,96 @@ +/***************************************************************************** + + 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 + 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. + + *****************************************************************************/ + +/***************************************************************************** + + sc_ob_event.h -- Event that fires only when the simulator arrives at the + notification time, or there are subsequent events + + Original Author: Mark burton + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable + CHANGE LOG AT END OF FILE + *****************************************************************************/ + +#ifndef _SC_OB_EVENT_ +#define _SC_OB_EVENT_ + +#include + +namespace sc_core { +class sc_ob_event : public sc_core::sc_module, + public sc_core::sc_event, + public sc_core::sc_stage_callback_if { + +private: + sc_core::sc_time m_scheduled; + sc_core::sc_process_handle m_th; + + void future_events_notify_th() { + while (true) { + m_th.suspend(); + + auto now = sc_core::sc_time_stamp(); + if (now >= m_scheduled) { + sc_core::sc_event::notify(); + } else { + sc_core::sc_event::notify(m_scheduled - now); + } + } + } + +public: + sc_ob_event(const sc_core::sc_module_name &n = + sc_core::sc_gen_unique_name("sc_ob_event")) + : sc_module(n), m_scheduled(sc_core::SC_ZERO_TIME) { + SC_THREAD(future_events_notify_th); + m_th = sc_core::sc_get_current_process_handle(); + } + void notify() { + sc_core::sc_event::notify(); + sc_core::sc_unregister_stage_callback(*this, sc_core::SC_POST_UPDATE); + } + void notify(double delay, sc_core::sc_time_unit unit) { + notify(sc_core::sc_time(delay, unit)); + } + void notify(sc_core::sc_time delay) { + sc_core::sc_event::cancel(); + m_scheduled = sc_core::sc_time_stamp() + delay; + sc_core::sc_register_stage_callback(*this, sc_core::SC_POST_UPDATE); + } + void cancel() { + sc_core::sc_event::cancel(); + sc_core::sc_unregister_stage_callback(*this, sc_core::SC_POST_UPDATE); + } + void stage_callback(const sc_core::sc_stage &stage) { + sc_core::sc_time pending = sc_core::sc_time_stamp(); + if (sc_core::sc_pending_activity_at_future_time()) { + pending += sc_core::sc_time_to_pending_activity(); + } + + if (pending >= m_scheduled) { + m_th.resume(); + sc_core::sc_unregister_stage_callback(*this, sc_core::SC_POST_UPDATE); + } + } + + ~sc_ob_event() {} +}; +} // namespace sc_core + +#endif // _SC_OB_EVENT_ \ No newline at end of file From 82cb38b6790d42f801d1edb7a55640335d4b3274 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 7 May 2026 13:55:57 +0200 Subject: [PATCH 47/50] Add sc_sync_window utility 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 --- src/sysc/utils/sc_sync_window.h | 222 ++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 src/sysc/utils/sc_sync_window.h diff --git a/src/sysc/utils/sc_sync_window.h b/src/sysc/utils/sc_sync_window.h new file mode 100644 index 000000000..0bfab66ec --- /dev/null +++ b/src/sysc/utils/sc_sync_window.h @@ -0,0 +1,222 @@ +/***************************************************************************** + + 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 + 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. + + *****************************************************************************/ + +/***************************************************************************** + + sc_sync_window.h -- keeps SystemC within a 'window' of time + + Original Author: Mark burton + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable + CHANGE LOG AT END OF FILE + *****************************************************************************/ + +#include +#include +#include +#include + +namespace sc_core { + +/** + * @brief Base class which gives the quantum (dynamically) and specifies if the + * window should stay open indefinitely or detach when there are no more events. + * A SC_ZERO_TIME quantum means sync on every time step + */ +struct sc_sync_policy_base { + virtual sc_core::sc_time quantum() = 0; + virtual bool keep_alive() = 0; +}; +struct sc_sync_policy_tlm_quantum : public sc_sync_policy_base { + sc_core::sc_time quantum() { + return tlm_utils::tlm_quantumkeeper::get_global_quantum(); + } + bool keep_alive() { return true; } +}; +struct sc_sync_policy_in_sync : public sc_sync_policy_base { + sc_core::sc_time quantum() { + return sc_core::sc_pending_activity() + ? sc_core::sc_time_to_pending_activity() + : sc_core::SC_ZERO_TIME; + } + bool keep_alive() { return false; } +}; +/** + * @brief windowed synchronizer, template sc_sync_policy gives the quantum + * (dynamically) and specifies if the window should stay open indefinitely or + * detach when there are no more events. + * + * @tparam sc_sync_policy + */ +template +class sc_sync_windowed : public sc_core::sc_module, + public sc_core::sc_prim_channel { + static_assert(std::is_base_of::value, + "sc_sync_policy must derive from sc_sync_policy_base"); + + sc_core::sc_event m_sweep_ev; + sc_ob_event m_step_ev; + sc_event m_update_ev; + std::mutex m_mutex; + sc_sync_policy policy; + +public: + struct window { + sc_core::sc_time from; + sc_core::sc_time to; + bool operator==(const window &other) const { + return other.to == to && other.from == from; + } + }; + + static inline const struct window zero_window = {sc_core::SC_ZERO_TIME, + sc_core::SC_ZERO_TIME}; + static inline const struct window open_window = {sc_core::SC_ZERO_TIME, + sc_core::sc_max_time()}; + +private: + window m_window; + window m_incomming_window; // used to hold the window values coming in from + // the other side. + + std::function m_other_async_set_window_fn; + + void do_other_async_set_window_fn(window w) { + if (m_other_async_set_window_fn) { + m_other_async_set_window_fn(w); + } + } + + /* Handle suspending/resuming at the end of the window, also inform other side + * if we reach end of window */ + void step_helper() { + auto now = sc_core::sc_time_stamp(); + auto to = m_window.to; + + /* The step helper has to handle both suspend and resume (because of + * SystemC) */ + if (now >= to) { + sc_core::sc_unsuspend_all(); // such that pending activity is valid if + // it's needed below. + + /* We should suspend at this point, and wait for the other side to catch + * up */ + do_other_async_set_window_fn({now, now + policy.quantum()}); + + if (!policy.keep_alive()) + async_attach_suspending(); + sc_core::sc_suspend_all(); + + } else { + /* the only way to get here is if we have a 'new' window from the other + * side. we are here just to unsuspend */ + sc_core::sc_unsuspend_all(); + if (!policy.keep_alive()) + async_detach_suspending(); + //do_other_async_set_window_fn({now, now + policy.quantum()}); + + /* Re-notify event - maybe presumably moved */ + m_step_ev.notify(to - now); + } + } + + /* + * Handle all sweep requests, once we arrive at a sweep point, tell the other + * side. + */ + void sweep_helper() { + auto now = sc_core::sc_time_stamp(); + do_other_async_set_window_fn({now, now + policy.quantum()}); + } + + /* Manage the Sync aync update */ + void update() { + std::lock_guard lg(m_mutex); + // Now we are on our thread, it's safe to update our window. + m_window = m_incomming_window; + auto now = sc_core::sc_time_stamp(); + + if (m_window.from > now) { + m_sweep_ev.notify(m_window.from - now); // Try to move time forward. + } else { + m_sweep_ev.cancel(); // no need to fire event. + } + /* let stepper handle suspend/resume, must time notify */ + m_update_ev.notify(sc_core::SC_ZERO_TIME); +// std::ostringstream s; +// s << "Got Window: " << m_window.from << " - " << m_window.to; +// SC_REPORT_INFO(sc_core::sc_module::name(), s.str().c_str()); + } + +public: + /* API call from other pair, presumably in a different thread. + * The internal window wil be updated atomically. + * + * Input: window - Window to set for sync. Sweep till the 'from' and step to + * the 'to'. + */ + void async_set_window(const window &w) { + /* Only accept updated windows so we dont re-send redundant updates + * safe at this point to compair against m_window as we took the lock + */ + std::lock_guard lg(m_mutex); + m_incomming_window = w; + if (!(w == m_window)) { + async_request_update(); + } + } + void detach() { + async_detach_suspending(); + m_other_async_set_window_fn(open_window); + } + void bind(sc_sync_windowed *other) { + if (m_other_async_set_window_fn) { + SC_REPORT_WARNING( + "sc_sync_window", + "m_other_async_set_window_fn was already registered or other " + "sc_sync_windowed was already bound!"); + } + m_other_async_set_window_fn = std::bind(&sc_sync_windowed::async_set_window, other, std::placeholders::_1); + } + void register_sync_cb(std::function fn) { + if (m_other_async_set_window_fn) { + SC_REPORT_WARNING( + "sc_sync_window", + "m_other_async_set_window_fn was already registered or other " + "sc_sync_windowed was already bound!"); + } + m_other_async_set_window_fn = fn; + } + SC_CTOR(sc_sync_windowed) + : m_window({sc_core::SC_ZERO_TIME, policy.quantum()}) { + + SC_METHOD(sweep_helper); + dont_initialize(); + sensitive << m_sweep_ev; + + SC_METHOD(step_helper); + dont_initialize(); + sensitive << m_step_ev << m_update_ev; + + m_step_ev.notify(policy.quantum()); + + this->sc_core::sc_prim_channel::async_attach_suspending(); + } +}; + +} // namespace sc_core \ No newline at end of file From ca7201a78eb180648b58e03515866f9daf49a5dd Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 12 May 2026 16:03:20 +0200 Subject: [PATCH 48/50] Add sc_concurrent / SC_ALLOW_CONCURRENCY with bidirectional stop and example sc_concurrent 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: Class template sc_concurrent_safe 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 --- examples/sysc/CMakeLists.txt | 1 + examples/sysc/concurrent/CMakeLists.txt | 32 +++ examples/sysc/concurrent/concurrent_test.cpp | 192 ++++++++++++++ src/sysc/utils/sc_concurrent.h | 256 +++++++++++++++++++ 4 files changed, 481 insertions(+) create mode 100644 examples/sysc/concurrent/CMakeLists.txt create mode 100644 examples/sysc/concurrent/concurrent_test.cpp create mode 100644 src/sysc/utils/sc_concurrent.h diff --git a/examples/sysc/CMakeLists.txt b/examples/sysc/CMakeLists.txt index 60a3eafd6..be0fbbeb1 100644 --- a/examples/sysc/CMakeLists.txt +++ b/examples/sysc/CMakeLists.txt @@ -65,4 +65,5 @@ add_subdirectory (simple_bus) add_subdirectory (simple_fifo) add_subdirectory (simple_perf) add_subdirectory (async_suspend) +add_subdirectory (concurrent) diff --git a/examples/sysc/concurrent/CMakeLists.txt b/examples/sysc/concurrent/CMakeLists.txt new file mode 100644 index 000000000..3a220aed7 --- /dev/null +++ b/examples/sysc/concurrent/CMakeLists.txt @@ -0,0 +1,32 @@ +############################################################################### +# +# 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. +# +############################################################################### + +############################################################################### +# +# examples/sysc/concurrent/CMakeLists.txt -- +# Build the SC_ALLOW_CONCURRENCY demonstrator. +# +############################################################################### + +set(THREADS_PREFER_PTHREAD_FLAG TRUE) +find_package(Threads REQUIRED) + +add_executable(concurrent_test concurrent_test.cpp) +target_link_libraries(concurrent_test SystemC::systemc Threads::Threads) +configure_and_add_test(concurrent_test) diff --git a/examples/sysc/concurrent/concurrent_test.cpp b/examples/sysc/concurrent/concurrent_test.cpp new file mode 100644 index 000000000..e0f6c908d --- /dev/null +++ b/examples/sysc/concurrent/concurrent_test.cpp @@ -0,0 +1,192 @@ +#include +#include + +#include +#include + +#include "sysc/utils/sc_concurrent.h" +#include + +std::mutex mutex; + +int do_work() { + double f = 0; + for (double j = 0; j < 100; j++) { + for (double k = 0; k < 100; k++) { + f += (j * k); + } + } + return f; +} +sc_core::sc_time time1 = SC_ZERO_TIME; +sc_core::sc_time time2 = SC_ZERO_TIME; +sc_core::sc_time worst = SC_ZERO_TIME; + +std::mutex tm; +void snaptime(sc_time &time) { + std::lock_guard lg(tm); + time = sc_time_stamp(); + auto d = (time1 > time2) ? time1 - time2 : time2 - time1; + if (d > worst) + worst = d; +} + +SC_MODULE(my_parallel_module) { + sc_event send_txn; + + void t1() { SC_REPORT_INFO(name(), "T1"); } + void t2() { + SC_REPORT_INFO(name(), "T2"); + wait(1, sc_core::SC_NS); + SC_REPORT_INFO(name(), "T2 1ns finished wait"); + } + + void t3() { + SC_REPORT_INFO(name(), "T3"); + for (int i = 0; i < 150000; i++) { + SC_REPORT_INFO(name(), "T3 waiting"); + wait(100, sc_core::SC_MS); + snaptime(time1); + + SC_REPORT_INFO(name(), "T3 do work"); + int f = do_work(); + SC_REPORT_INFO(name(), "Work done"); + + SC_REPORT_INFO(name(), "T3 done waiting"); + } + SC_REPORT_INFO(name(), "T3 finished loop"); + } + + void t4() { + while (1) { + wait(send_txn); + + SC_REPORT_INFO(name(), "Send TXN"); + tlm::tlm_generic_payload trans; + sc_core::sc_time delay = sc_core::SC_ZERO_TIME; + + socket->b_transport(trans, delay); + SC_REPORT_INFO(name(), "done TXN"); + } + } + void m1() { + + SC_REPORT_INFO( + name(), + ("Got signal " + std::string(in.read() ? "True" : "False")).c_str()); + + send_txn.notify(10, sc_core::SC_MS); + } + +public: + SC_CTOR(my_parallel_module) { + SC_REPORT_INFO(name(), "Ading tasks t1,t2 and t3\n"); + SC_THREAD(t1); + SC_THREAD(t2); + SC_THREAD(t3); + SC_THREAD(t4); + + SC_METHOD(m1); + sensitive << in; + dont_initialize(); + + SC_REPORT_INFO(name(), "Constructor done"); + } + + void end_of_elaboration() { SC_REPORT_INFO(name(), "end_of_elaboration"); } + void start_of_simulation() { SC_REPORT_INFO(name(), "start_of_simulation"); } + sc_in in; + + tlm_utils::simple_initiator_socket socket; +}; + +SC_MODULE(My_normal_module) { + + tlm_utils::simple_target_socket socket; + + void t1() { SC_REPORT_INFO(name(), "T1"); } + void t2() { + SC_REPORT_INFO(name(), "T2"); + wait(1, sc_core::SC_NS); + + SC_REPORT_INFO(name(), "T2 1ns finished wait"); + } + + void t3() { + SC_REPORT_INFO(name(), "T3"); + for (int i = 0; i < 150000; i++) { + SC_REPORT_INFO(name(), "T3 waiting"); + wait(100, sc_core::SC_MS); + snaptime(time2); + + SC_REPORT_INFO(name(), "T3 do work"); + + int f = do_work(); + SC_REPORT_INFO(name(), "Work done"); + + SC_REPORT_INFO( + name(), ("Sending signal" + std::string((i & 0x1) ? "True" : "False")) + .c_str()); + // out.write(i & 0x1); + SC_REPORT_INFO(name(), "T3 done waiting"); + } + SC_REPORT_INFO(name(), "T3 finished loop"); + sc_stop(); + } + void b_transport(tlm::tlm_generic_payload & trans, sc_core::sc_time & delay) { + SC_REPORT_INFO(name(), "Got b_transport"); + } + SC_CTOR(My_normal_module) { + SC_THREAD(t1); + SC_THREAD(t2); + SC_THREAD(t3); + socket.register_b_transport(this, &My_normal_module::b_transport); + } + sc_out out; + + void end_of_elaboration() { SC_REPORT_INFO(name(), "end_of_elaboration"); } + void start_of_simulation() { SC_REPORT_INFO(name(), "start_of_simulation"); } +}; + +void report_handler(const sc_core::sc_report &rep, + const sc_core::sc_actions &actions) { +#if DEBUG + std::lock_guard lock(mutex); + auto now = std::chrono::high_resolution_clock::now(); + auto now_ms = std::chrono::duration_cast( + now.time_since_epoch()) + .count(); + + cout << now_ms % 10000 << " thread:" << std::this_thread::get_id() + << " simcontext:" << sc_core::sc_get_curr_simcontext() + << " time:" << sc_core::sc_time_stamp() << " " + << " : [" << rep.get_msg_type() << "] " << rep.get_msg() << std::endl; +#endif +} + +int sc_main(int argc, char *argv[]) { + + ::sc_core::sc_report_handler::set_verbosity_level(sc_core::SC_DEBUG); + ::sc_core::sc_report_handler::set_handler(report_handler); + + tlm_utils::tlm_quantumkeeper::set_global_quantum( + sc_core::sc_time(200, sc_core::SC_MS)); + + My_normal_module mn("Normal"); + //my_parallel_module mp("Parallel"); + SC_ALLOW_CONCURRENCY(my_parallel_module, sc_core::sc_sync_policy_tlm_quantum) mp("Parallel"); + //SC_ALLOW_CONCURRENCY(my_parallel_module, sc_core::sc_sync_policy_in_sync) mp("Parallel"); + + sc_signal sig; + mn.out(sig); + mp.in(sig); + + mp.socket.bind(mn.socket); + + SC_REPORT_INFO("main", "before start"); + sc_start(); + std::cout << "Worst diff: " << worst << "\n"; + SC_REPORT_INFO("main", "finished"); + + return 0; +} \ No newline at end of file diff --git a/src/sysc/utils/sc_concurrent.h b/src/sysc/utils/sc_concurrent.h new file mode 100644 index 000000000..5727dc765 --- /dev/null +++ b/src/sysc/utils/sc_concurrent.h @@ -0,0 +1,256 @@ +/***************************************************************************** + + 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 + 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. + + *****************************************************************************/ + +/***************************************************************************** + + sc_concurrent.h -- Mark an SC_MODULE as safe to run concurrently with + others. Whether the implementation actually exploits this for parallel + execution is implementation-defined; the API is the user's promise that + the module's interactions with the rest of the elaboration are bounded + to its sync_window and to thread-safe channels. + + Original Author: Mark Burton + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable + CHANGE LOG AT END OF FILE + *****************************************************************************/ + +#ifndef SC_CONCURRENT_H +#define SC_CONCURRENT_H + +#include "sc_sync_window.h" +#include + +#include +#include +#include +#include + +namespace sc_core { + +// Module-author opt-out. Default == every module is safe to wrap in +// sc_concurrent. An author who knows their module is fundamentally +// unsafe for concurrent execution may specialise this to false_type; +// sc_concurrent's static_assert below then turns any attempt to wrap +// the module into a compile-time error. +template +struct sc_concurrent_safe : std::true_type {}; + +template class sc_concurrent; +/** + * @brief Simcontext handler, encapsulates a simcontext + * This is required for the templated class to work, and hence is required in + * the header. It should not be used directly + * @tparam SYNC_POLICY + */ +template +class _internal_simcontext_handler { + template friend class sc_concurrent; + /** + * @brief Helper to capture start of simulation and start std::thread + * + */ + class control_module : public sc_core::sc_module, + public sc_core::sc_stage_callback_if { + class _internal_simcontext_handler *m_simcontext_h; + + std::thread m_thread; + std::mutex m_mutex; + std::condition_variable m_cv; + + // Prim channel purely to accept async_request_update, to call sc_stop. + class terminator_pc : public sc_core::sc_prim_channel { + void update() { sc_stop(); } + + public: + // terminator_pc() {} + void terminate() { async_request_update(); } + }; + terminator_pc *m_terminator; + sc_event *m_end_ev; + bool started = false; + // Captured on the parent thread in start_of_simulation(), applied on + // the child thread in sc_context_start() so the child inherits the + // parent's sc_report_handler configuration (handler, verbosity, + // suppress/force masks, sev_actions, log_file_name). Counters are + // not in the snapshot — the child starts fresh. + sc_core::sc_report_handler::config_snapshot m_report_config; + void sc_context_start() { + sc_core::sc_report_handler::apply_config(m_report_config); + m_simcontext_h->use_simcontext(); + // Register on the child's stage_callback_registry: POST_START so the + // parent's thread can stop blocking once we are up, and POST_END so we + // can wake the parent if the child's sc_start was the one that ended. + sc_core::sc_register_stage_callback(*this, + sc_core::SC_POST_START_OF_SIMULATION + | sc_core::SC_POST_END_OF_SIMULATION); + sc_core::sc_start(); + m_sync_child->detach(); + } + + public: + void start_of_simulation() { + m_report_config = sc_core::sc_report_handler::snapshot_config(); + m_thread = std::thread(&control_module::sc_context_start, this); + std::unique_lock lg(m_mutex); + m_cv.wait(lg, [this]() { return started; }); + } + void end_thread() { sc_core::sc_stop(); } + void end_of_simulation() { + // Drive the child to stop, then wait for it to fully finish + // BEFORE proceeding with the rest of the parent's teardown. + // Reason: the child and the parent share an sc_object_manager + // (see sc_simcontext::init's sub-sim handling). The hierarchy + // navigation stack m_object_stack is a single shared vector; + // sc_port_base::simulation_done on every port pushes/pops it + // via get_hierarchy_scope. If the parent runs its + // port_registry::simulation_done while the child is still + // tearing down its own ports, both threads concurrently + // mutate that stack and break its discipline. Joining here - + // before the parent's port simulation_done callbacks run - + // serialises the two teardowns and avoids the race. + m_terminator->terminate(); + if (m_thread.joinable() && + m_thread.get_id() != std::this_thread::get_id()) + m_thread.join(); + } + void stage_callback(const sc_core::sc_stage &stage) { + switch (stage) { + case sc_core::SC_POST_END_OF_SIMULATION: + if (sc_core::sc_get_curr_simcontext() == &m_simcontext_h->m_simcontext) { + // Child-side POST_END: wake the parent so it stops too. + // m_end_ev lives in the parent; notify(SC_ZERO_TIME) from the + // child's thread is routed via sc_simcontext::run_update_async + // to the parent's update phase, which delta-schedules the event; + // SC_METHOD(end_thread) then fires on the parent and calls + // sc_stop(). Use the delta variant rather than the immediate + // notify(): the cross-context callback runs in the parent's + // update phase, where immediate notification would error out + // ("immediate notification outside evaluation phase"). + // This handles the child-initiated stop; the parent-initiated + // direction is already covered by end_of_simulation() above + // (which also joins this thread). + m_end_ev->notify(sc_core::SC_ZERO_TIME); + } + // No parent-side join here any more; we joined in + // end_of_simulation(), which fires before any port's + // simulation_done callback. + break; + // Called by the child thread once it's up and running + case sc_core::SC_POST_START_OF_SIMULATION: { + std::lock_guard lg(m_mutex); + started = true; + m_cv.notify_all(); + break; + } + default: + break; + } + } + + ~control_module() { + delete m_end_ev; + delete m_terminator; + } + + SC_CTOR(control_module, _internal_simcontext_handler *p) + : m_simcontext_h(p) { + m_simcontext_h->use_simcontext(); + SYNC_POLICY sync_policy; + m_sync_child = new sc_sync_windowed("sync_child"); + m_terminator = new terminator_pc(); + + m_simcontext_h->revert_simcontext(); + + sc_core::sc_register_stage_callback(*this, + sc_core::SC_POST_END_OF_SIMULATION); + + m_end_ev = new sc_core::sc_event(); + SC_METHOD(end_thread); + sensitive << *m_end_ev; + dont_initialize(); + } + sc_sync_windowed *m_sync_child; + }; + +private: + sc_core::sc_simcontext *m_old_simcontext = + sc_get_curr_simcontext(); // Side effect create simcontext if we are the + // first module + sc_core::sc_simcontext m_simcontext; + + control_module m_ctrl_module; + sc_sync_windowed m_sync_parent; + + void use_simcontext() { + assert(sc_curr_simcontext != &m_simcontext); + m_old_simcontext = sc_curr_simcontext; + sc_curr_simcontext = &m_simcontext; + } + + void revert_simcontext() { sc_curr_simcontext = m_old_simcontext; } + +public: + _internal_simcontext_handler() + // sc_gen_unique_name keeps each wrapper's internal modules + // distinct in the shared sc_object_manager. Hardcoded names + // would collide across multiple SC_ALLOW_CONCURRENCY wrappers + // and trigger an "object already exists" rename warning. + : m_ctrl_module(sc_core::sc_gen_unique_name("ctrl_module"), this), + m_sync_parent(sc_core::sc_gen_unique_name("sync_parent")) { + SYNC_POLICY sync_policy; + + m_ctrl_module.m_sync_child->bind(&m_sync_parent); + m_sync_parent.bind(m_ctrl_module.m_sync_child); + + use_simcontext(); + } +}; + +/** + * @brief sc_concurrent marks a module as safe to run concurrently with + * other modules. It ensures _internal_simcontext_handler is constructed + * first (creating the child simcontext), the simcontext is switched, + * the wrapped module T is then constructed in that child simcontext, + * and the simcontext is restored afterwards. + * + * Whether the runtime actually spins a separate thread for the child + * simcontext is implementation-defined. The user's contract is only + * that this module's interactions with the rest of the design are + * bounded to its sync_window and thread-safe channels. + * + * @tparam T + * @tparam SYNC_POLICY + */ +template +class sc_concurrent : public _internal_simcontext_handler, public T { + static_assert(sc_concurrent_safe::value, + "module type is not declared sc_concurrent_safe " + "(specialise sc_concurrent_safe to std::false_type " + "to forbid wrapping; default is true)"); +public: + template sc_concurrent(A... a) : T(a...) { + _internal_simcontext_handler::revert_simcontext(); + } +}; + +#define SC_ALLOW_CONCURRENCY(mod, policy) sc_core::sc_concurrent + +} // namespace sc_core + +#endif // SC_CONCURRENT_H \ No newline at end of file From a7068d0e84d2079a8df5c266eeff78a37412a57c Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 12 May 2026 08:51:16 +0200 Subject: [PATCH 49/50] Complete sc_object_manager thread safety for shared sub-simcontexts 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 --- src/sysc/kernel/sc_object_manager.cpp | 30 ++++++++----- src/sysc/kernel/sc_object_manager.h | 13 ++++-- src/sysc/kernel/sc_runnable_int.h | 65 ++++++++++++++++++++++----- src/sysc/kernel/sc_simcontext.cpp | 25 +++++++++++ src/sysc/kernel/sc_simcontext.h | 24 ++++++++++ 5 files changed, 131 insertions(+), 26 deletions(-) diff --git a/src/sysc/kernel/sc_object_manager.cpp b/src/sysc/kernel/sc_object_manager.cpp index b7f22d723..a3cd6f43c 100644 --- a/src/sysc/kernel/sc_object_manager.cpp +++ b/src/sysc/kernel/sc_object_manager.cpp @@ -55,13 +55,15 @@ namespace sc_core { // Manager of objects. // ---------------------------------------------------------------------------- +// Per-thread construction-context storage. See comment in the header. +thread_local sc_module_name* sc_object_manager::m_module_name_stack = 0; +thread_local sc_object_manager::object_vector_t sc_object_manager::m_object_stack; + sc_object_manager::sc_object_manager() : m_event_it(), m_event_walk_ok(0), m_instance_table(), - m_module_name_stack(0), m_object_it(), - m_object_stack(), m_object_walk_ok() { } @@ -99,7 +101,7 @@ sc_object_manager::~sc_object_manager() // +---------------------------------------------------------------------------- std::string sc_object_manager::create_name(const char* leaf_name) { - std::unique_lock lock(m_mutex); + std::lock_guard lock(m_mutex); bool clash; // true if path name exists in obj table std::string leafname_string; // string containing the leaf name. @@ -171,6 +173,7 @@ std::string sc_object_manager::create_name(const char* leaf_name) bool sc_object_manager::name_exists(const std::string& name) { + std::lock_guard lock(m_mutex); instance_table_t::const_iterator it = m_instance_table.find(name); return (it != m_instance_table.end()) && (it->second.m_name_origin != SC_NAME_NONE); @@ -189,6 +192,7 @@ sc_object_manager::name_exists(const std::string& name) const char* sc_object_manager::get_name(const std::string& name) { + std::lock_guard lock(m_mutex); instance_table_t::iterator it = m_instance_table.find(name); if (it != m_instance_table.end() && it->second.m_name_origin != SC_NAME_NONE) { @@ -211,6 +215,7 @@ sc_object_manager::get_name(const std::string& name) sc_event* sc_object_manager::find_event(const char* name) { + std::lock_guard lock(m_mutex); instance_table_t::iterator it; it = m_instance_table.find(name); if(it != m_instance_table.end() @@ -235,6 +240,7 @@ sc_object_manager::find_event(const char* name) sc_object* sc_object_manager::find_object(const char* name) { + std::lock_guard lock(m_mutex); instance_table_t::iterator it; it = m_instance_table.find(name); if(it != m_instance_table.end() @@ -257,12 +263,13 @@ sc_object_manager::find_object(const char* name) sc_object* sc_object_manager::first_object() { + std::lock_guard lock(m_mutex); sc_object* result_p; // result to return. m_object_walk_ok = true; result_p = NULL; - for ( m_object_it = m_instance_table.begin(); - m_object_it != m_instance_table.end(); + for ( m_object_it = m_instance_table.begin(); + m_object_it != m_instance_table.end(); m_object_it++ ) { if(m_object_it->second.m_name_origin == SC_NAME_OBJECT) { @@ -339,7 +346,7 @@ sc_object_manager::hierarchy_size() bool sc_object_manager::insert_external_name(const std::string& name) { - std::unique_lock lock(m_mutex); + std::lock_guard lock(m_mutex); if(!name_exists(name)) { m_instance_table[name].m_element_p = NULL; @@ -372,7 +379,7 @@ sc_object_manager::insert_external_name(const std::string& name) void sc_object_manager::insert_event(const std::string& name, sc_event* event_p) { - std::unique_lock lock(m_mutex); + std::lock_guard lock(m_mutex); m_instance_table[name].m_element_p = static_cast(event_p); m_instance_table[name].m_name_origin = SC_NAME_EVENT; @@ -391,7 +398,7 @@ sc_object_manager::insert_event(const std::string& name, sc_event* event_p) void sc_object_manager::insert_object(const std::string& name, sc_object* object_p) { - std::unique_lock lock(m_mutex); + std::lock_guard lock(m_mutex); m_instance_table[name].m_element_p = static_cast(object_p); m_instance_table[name].m_name_origin = SC_NAME_OBJECT; @@ -405,6 +412,7 @@ sc_object_manager::insert_object(const std::string& name, sc_object* object_p) sc_object* sc_object_manager::next_object() { + std::lock_guard lock(m_mutex); sc_object* result_p; // result to return. sc_assert( m_object_walk_ok ); @@ -487,7 +495,7 @@ sc_object_manager::top_of_module_name_stack_name() const void sc_object_manager::remove_event(const std::string& name) { - std::unique_lock lock(m_mutex); + std::lock_guard lock(m_mutex); instance_table_t::iterator it; // instance table iterator. it = m_instance_table.find(name); @@ -511,7 +519,7 @@ sc_object_manager::remove_event(const std::string& name) void sc_object_manager::remove_object(const std::string& name) { - std::unique_lock lock(m_mutex); + std::lock_guard lock(m_mutex); instance_table_t::iterator it; // instance table iterator. it = m_instance_table.find(name); @@ -535,7 +543,7 @@ sc_object_manager::remove_object(const std::string& name) bool sc_object_manager::remove_external_name(const std::string& name) { - std::unique_lock lock(m_mutex); + std::lock_guard lock(m_mutex); instance_table_t::iterator it; // instance table iterator. it = m_instance_table.find(name); diff --git a/src/sysc/kernel/sc_object_manager.h b/src/sysc/kernel/sc_object_manager.h index eccad562e..9fc83533a 100644 --- a/src/sysc/kernel/sc_object_manager.h +++ b/src/sysc/kernel/sc_object_manager.h @@ -110,10 +110,17 @@ class sc_object_manager instance_table_t::iterator m_event_it; // event instance iterator. bool m_event_walk_ok; // true if can walk events. instance_table_t m_instance_table; // table of instances. - sc_module_name* m_module_name_stack; // sc_module_name stack. - std::mutex m_mutex; // Mutex to ensure thread safety. + // m_module_name_stack and m_object_stack are per-thread construction + // context: each thread that constructs sc_objects has its own nesting + // of modules and of sc_module_names. Making them thread_local avoids + // cross-thread interference when multiple sibling simcontexts (running + // on their own std::threads) share this object_manager. + static thread_local sc_module_name* m_module_name_stack; // sc_module_name stack (per thread). + // Recursive because public methods that take the lock (e.g. create_name) + // call back into other public methods (name_exists) that also take the lock. + mutable std::recursive_mutex m_mutex; // Mutex to ensure thread safety. instance_table_t::iterator m_object_it; // object instance iterator. - object_vector_t m_object_stack; // sc_object stack. + static thread_local object_vector_t m_object_stack; // sc_object stack (per thread). bool m_object_walk_ok; // true if can walk objects. }; diff --git a/src/sysc/kernel/sc_runnable_int.h b/src/sysc/kernel/sc_runnable_int.h index 06246a84b..b82da9c84 100644 --- a/src/sysc/kernel/sc_runnable_int.h +++ b/src/sysc/kernel/sc_runnable_int.h @@ -36,6 +36,8 @@ #include "sysc/kernel/sc_runnable.h" #include "sysc/kernel/sc_method_process.h" #include "sysc/kernel/sc_thread_process.h" +#include "sysc/kernel/sc_simcontext.h" +#include "sysc/kernel/sc_object_manager.h" // DEBUGGING MACROS: // @@ -145,24 +147,63 @@ inline void sc_runnable::execute_thread_next( sc_thread_handle thread_h ) //------------------------------------------------------------------------------ inline void sc_runnable::init() { - if ( !m_methods_push_head ) + // These list-head sentinels are kernel-internal dummies, one pair + // per sc_runnable (i.e. one pair per sc_simcontext). When two + // simcontexts share an sc_object_manager (the parallel-sim case), + // a hardcoded name like "methods_push_head" would collide between + // them; the second thread to register would race the first on the + // shared instance_table. + // + // To give each simcontext's sentinels a distinct and readable name, + // we temporarily push the simcontext's first top-level module onto + // the hierarchy so the sentinel is constructed as its child. The + // sentinel's full name then naturally becomes + // ".methods_push_head" + // e.g. "prod.methods_push_head" in the child simcontext of an + // SC_ALLOW_CONCURRENCY(prod, ...) wrapper. detach() immediately + // after construction removes it from the parent's child list and + // the shared name table; only the already-unique full name lives on. + // + // Called from prepare_to_simulate, so sc_main has finished and the + // simcontext's child list is already populated. + if ( !m_methods_push_head || !m_threads_push_head ) { - m_methods_push_head = new sc_method_process("methods_push_head", true, - sc_entry_func(), 0, 0); - m_methods_push_head->dont_initialize(true); - m_methods_push_head->detach(); + sc_simcontext* sim = sc_get_curr_simcontext(); + sc_object_host* parent = sim->first_top_level_host(); + + // Push `parent` on the hierarchy stack so the sentinels' + // sc_object_init picks it up via active_object() as their + // parent. Their full names then naturally become e.g. + // "prod.methods_push_head". Pop again before detach() runs + // (detach unhooks them from parent's child list and the + // shared name table; only the unique full name lives on). + sc_object_manager* om = sim->get_object_manager(); + if ( parent ) om->hierarchy_push( parent ); + + if ( !m_methods_push_head ) + { + m_methods_push_head = new sc_method_process("methods_push_head", + true, sc_entry_func(), + 0, 0); + m_methods_push_head->dont_initialize(true); + m_methods_push_head->detach(); + } + if ( !m_threads_push_head ) + { + m_threads_push_head = new sc_thread_process("threads_push_head", + true, sc_entry_func(), + 0, 0); + m_threads_push_head->dont_initialize(true); + m_threads_push_head->detach(); + } + + if ( parent ) om->hierarchy_pop(); } + m_methods_pop = SC_NO_METHODS; m_methods_push_tail = m_methods_push_head; m_methods_push_head->set_next_runnable(SC_NO_METHODS); - if ( !m_threads_push_head ) - { - m_threads_push_head = new sc_thread_process("threads_push_head", true, - sc_entry_func(), 0, 0); - m_threads_push_head->dont_initialize(true); - m_threads_push_head->detach(); - } m_threads_pop = SC_NO_THREADS; m_threads_push_head->set_next_runnable(SC_NO_THREADS); m_threads_push_tail = m_threads_push_head; diff --git a/src/sysc/kernel/sc_simcontext.cpp b/src/sysc/kernel/sc_simcontext.cpp index 2394e33c0..a2a962aee 100644 --- a/src/sysc/kernel/sc_simcontext.cpp +++ b/src/sysc/kernel/sc_simcontext.cpp @@ -1335,6 +1335,31 @@ sc_simcontext::add_reset_finder( sc_reset_finder* reset_finder ) m_reset_finder_q = reset_finder; } +sc_object_host* +sc_simcontext::first_top_level_host() const +{ + // Find the first top-level sc_module child. We skip non-module + // children (sc_signal, sc_async_runnable_helper, etc.) so the + // result reflects user intent rather than whichever helper happens + // to be constructed first. Children are appended, never reordered, + // so the result is stable for the simcontext's lifetime. + for ( sc_object* o : m_child_objects ) { + if ( sc_module* mod = dynamic_cast( o ) ) + return mod; + } + return NULL; +} + +const char* +sc_simcontext::name() const +{ + // Use the basename of the first top-level sc_module child as the + // simcontext's logical name. Falls back to a kernel-internal + // sentinel name before any module is registered. + sc_object_host* top = first_top_level_host(); + return top ? top->basename() : SC_DEFAULT_SIMCONTEXT_NAME_; +} + const ::std::vector& sc_simcontext::get_child_objects() const { diff --git a/src/sysc/kernel/sc_simcontext.h b/src/sysc/kernel/sc_simcontext.h index c8fd1194f..6fbff8f47 100644 --- a/src/sysc/kernel/sc_simcontext.h +++ b/src/sysc/kernel/sc_simcontext.h @@ -74,6 +74,12 @@ class sc_prim_channel_registry; class sc_process_table; class sc_signal_bool_deval; class sc_trace_file; + +// Kernel-internal fallback name used for an sc_simcontext that has +// not yet acquired a name from a top-level child object. Using the +// class name keeps the style in line with other kernel-generated +// basenames ("object", "method_p", "thread_p", "invoker"). +#define SC_DEFAULT_SIMCONTEXT_NAME_ "sc_simcontext" class sc_runnable; class sc_process_host; class sc_method_process; @@ -273,6 +279,24 @@ class SC_API sc_simcontext sc_object_host* active_object(); + // A logical name for this simcontext. Today there is no field that + // holds an explicit simcontext name; we derive one from the basename + // of the first top-level sc_module child, falling back to + // SC_DEFAULT_SIMCONTEXT_NAME_ (a clearly kernel-internal token) when + // no module has been registered yet. Used to qualify kernel- + // internal names (e.g. sc_runnable's list-head sentinels) so that + // simcontexts sharing an sc_object_manager do not collide on those + // names. Returns a pointer that is stable for the simcontext's + // lifetime (basename storage is owned by the child object). + const char* name() const; + + // Returns the sc_object_host* backing name() above, or NULL if no + // top-level sc_module has been registered yet. Exposed so that + // kernel-internal code (e.g. sc_runnable::init, which anchors its + // list-head sentinels under this module for hierarchical naming) + // does not have to walk the deprecated get_child_objects() list. + sc_object_host* first_top_level_host() const; + sc_object* first_object(); sc_object* next_object(); sc_object* find_object( const char* name ); From b82a6aa850ee3f550d33fd7006c7fa6406227250 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 12 May 2026 08:52:31 +0200 Subject: [PATCH 50/50] Add cross-simcontext regression tests 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 --- .../concurrent/child_stop/child_stop.cpp | 123 +++++++++++++++ .../child_stop/golden/child_stop.log | 6 + .../cross_context_write.cpp | 120 ++++++++++++++ .../golden/cross_context_write.log | 6 + .../golden/multiple_modules.log | 8 + .../multiple_modules/multiple_modules.cpp | 146 ++++++++++++++++++ 6 files changed, 409 insertions(+) create mode 100644 tests/systemc/concurrent/child_stop/child_stop.cpp create mode 100644 tests/systemc/concurrent/child_stop/golden/child_stop.log create mode 100644 tests/systemc/concurrent/cross_context_write/cross_context_write.cpp create mode 100644 tests/systemc/concurrent/cross_context_write/golden/cross_context_write.log create mode 100644 tests/systemc/concurrent/multiple_modules/golden/multiple_modules.log create mode 100644 tests/systemc/concurrent/multiple_modules/multiple_modules.cpp diff --git a/tests/systemc/concurrent/child_stop/child_stop.cpp b/tests/systemc/concurrent/child_stop/child_stop.cpp new file mode 100644 index 000000000..5becb8b7f --- /dev/null +++ b/tests/systemc/concurrent/child_stop/child_stop.cpp @@ -0,0 +1,123 @@ +/***************************************************************************** + + 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. + + *****************************************************************************/ + +/***************************************************************************** + + child_stop.cpp -- Verify that sc_stop called from inside a + SC_ALLOW_CONCURRENCY module (running on the child simcontext's + thread) cleanly stops the whole simulation including the parent. + + Without the child->parent stop propagation, the parent would keep + running until its own events run out. We set up a parent module + that would otherwise run indefinitely (ticker that keeps arming + its own event), so if the child's sc_stop does not propagate, the + test hangs and the outer timeout kills it. + + Pass criteria: + * sc_start returns within the test timeout. + * The child's stop intent is observed (child_stopped flag). + * The parent's own ticker ran at least once but did NOT exhaust + itself, confirming the parent was stopped by the child rather + than by starvation. + + Exits 0 on pass, 1 on fail. + + *****************************************************************************/ + +#include +#include "sysc/utils/sc_concurrent.h" + +#include + +SC_MODULE(child_mod) +{ + bool* m_child_stopped; + + void run() + { + // Run for a few ms, then call sc_stop from the child's thread. + wait(5, sc_core::SC_MS); + *m_child_stopped = true; + sc_stop(); + } + + SC_HAS_PROCESS(child_mod); + child_mod(sc_module_name n, bool* stop_flag) + : sc_module(n), m_child_stopped(stop_flag) + { + SC_THREAD(run); + } +}; + +SC_MODULE(parent_ticker) +{ + int* m_tick_count; + sc_event m_tick; + + void tick() + { + ++(*m_tick_count); + // re-arm: this module would run forever if never stopped + m_tick.notify(sc_core::sc_time(1, sc_core::SC_MS)); + } + + SC_HAS_PROCESS(parent_ticker); + parent_ticker(sc_module_name n, int* tick_count) + : sc_module(n), m_tick_count(tick_count) + { + SC_METHOD(tick); + sensitive << m_tick; + // kick off + m_tick.notify(sc_core::sc_time(1, sc_core::SC_MS)); + } +}; + +int sc_main(int, char*[]) +{ + tlm_utils::tlm_quantumkeeper::set_global_quantum( + sc_core::sc_time(1, sc_core::SC_MS)); + + bool child_stopped = false; + int parent_ticks = 0; + + SC_ALLOW_CONCURRENCY(child_mod, sc_core::sc_sync_policy_tlm_quantum) + child("child", &child_stopped); + parent_ticker parent("parent", &parent_ticks); + + sc_start(); + + // Parent's ticker re-arms itself every 1 ms. If child's sc_stop + // did not propagate, the parent would tick forever. A bounded + // tick count + the outer timeout together catch both the "did + // not stop" case and the "stopped too early" case. + bool ok = + child_stopped && + parent_ticks >= 1 && + parent_ticks < 1000; + + // parent_ticks is timing-dependent (depends on how quickly the + // child schedules sc_stop), so it's deliberately omitted from the + // printed verdict to keep the golden log deterministic. The + // bounds check above is the real pass condition. + std::cout << "child_stopped=" << (child_stopped ? "yes" : "no") + << " result=" << (ok ? "PASS" : "FAIL") + << std::endl; + + return ok ? 0 : 1; +} diff --git a/tests/systemc/concurrent/child_stop/golden/child_stop.log b/tests/systemc/concurrent/child_stop/golden/child_stop.log new file mode 100644 index 000000000..a8e419c2c --- /dev/null +++ b/tests/systemc/concurrent/child_stop/golden/child_stop.log @@ -0,0 +1,6 @@ +SystemC Simulation + +Info: /OSCI/SystemC: Simulation stopped by user. + +Info: /OSCI/SystemC: Simulation stopped by user. +child_stopped=yes result=PASS diff --git a/tests/systemc/concurrent/cross_context_write/cross_context_write.cpp b/tests/systemc/concurrent/cross_context_write/cross_context_write.cpp new file mode 100644 index 000000000..8d1ad9cc7 --- /dev/null +++ b/tests/systemc/concurrent/cross_context_write/cross_context_write.cpp @@ -0,0 +1,120 @@ +/***************************************************************************** + + 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. + + *****************************************************************************/ + +/***************************************************************************** + + cross_context_write.cpp -- Verify sc_signal::write from a foreign + simcontext (the writer's process belongs to a SC_ALLOW_CONCURRENCY + module) lands correctly on the signal's owning simcontext, and the + reader (in the parent simcontext) observes every write. + + Producer is wrapped in SC_ALLOW_CONCURRENCY -- it lives in a child + simcontext. Consumer is in the parent. The signal that connects + them is constructed at sc_main scope (parent). Producer's + SC_THREAD writes a strictly-increasing sequence; Consumer's + SC_METHOD is sensitive to the signal and records every value seen. + After the expected number of values arrive, Consumer calls sc_stop. + + Pass criteria: + * Consumer method fires at least N times (one per write). + * Each successive observed value is strictly greater than the + previous (no torn read, no out-of-order values). + * The final observed value equals N. + + Exits 0 on pass, 1 on fail. + + *****************************************************************************/ + +#include +#include "sysc/utils/sc_concurrent.h" + +#include + +constexpr int N_WRITES = 5; +static const sc_core::sc_time WRITE_PERIOD(10, sc_core::SC_MS); + +SC_MODULE(producer) +{ + sc_out out; + + void run() + { + for (int i = 1; i <= N_WRITES; ++i) { + wait(WRITE_PERIOD); + out.write(i); + } + } + + SC_CTOR(producer) { SC_THREAD(run); } +}; + +SC_MODULE(consumer) +{ + sc_in in; + + int m_last_seen = 0; + int m_count = 0; + bool m_monotonic = true; + + void on_change() + { + int v = in.read(); + if (v <= m_last_seen) m_monotonic = false; + m_last_seen = v; + ++m_count; + if (v >= N_WRITES) sc_stop(); + } + + SC_CTOR(consumer) + { + SC_METHOD(on_change); + sensitive << in; + dont_initialize(); + } +}; + +int sc_main(int, char*[]) +{ + tlm_utils::tlm_quantumkeeper::set_global_quantum( + sc_core::sc_time(1, sc_core::SC_MS)); + + sc_signal sig; + + SC_ALLOW_CONCURRENCY(producer, sc_core::sc_sync_policy_tlm_quantum) + prod("prod"); + consumer cons("cons"); + + prod.out(sig); + cons.in(sig); + + sc_start(); + + bool ok = + cons.m_count >= N_WRITES && + cons.m_monotonic && + cons.m_last_seen == N_WRITES; + + std::cout << "count=" << cons.m_count + << " last_seen=" << cons.m_last_seen + << " monotonic=" << (cons.m_monotonic ? "yes" : "no") + << " result=" << (ok ? "PASS" : "FAIL") + << std::endl; + + return ok ? 0 : 1; +} diff --git a/tests/systemc/concurrent/cross_context_write/golden/cross_context_write.log b/tests/systemc/concurrent/cross_context_write/golden/cross_context_write.log new file mode 100644 index 000000000..facfac5f6 --- /dev/null +++ b/tests/systemc/concurrent/cross_context_write/golden/cross_context_write.log @@ -0,0 +1,6 @@ +SystemC Simulation + +Info: /OSCI/SystemC: Simulation stopped by user. + +Info: /OSCI/SystemC: Simulation stopped by user. +count=5 last_seen=5 monotonic=yes result=PASS diff --git a/tests/systemc/concurrent/multiple_modules/golden/multiple_modules.log b/tests/systemc/concurrent/multiple_modules/golden/multiple_modules.log new file mode 100644 index 000000000..aa57490c8 --- /dev/null +++ b/tests/systemc/concurrent/multiple_modules/golden/multiple_modules.log @@ -0,0 +1,8 @@ +SystemC Simulation + +Info: /OSCI/SystemC: Simulation stopped by user. + +Info: /OSCI/SystemC: Simulation stopped by user. + +Info: /OSCI/SystemC: Simulation stopped by user. +a:count=4 last=104 mono=yes b:count=4 last=204 mono=yes result=PASS diff --git a/tests/systemc/concurrent/multiple_modules/multiple_modules.cpp b/tests/systemc/concurrent/multiple_modules/multiple_modules.cpp new file mode 100644 index 000000000..0b732d460 --- /dev/null +++ b/tests/systemc/concurrent/multiple_modules/multiple_modules.cpp @@ -0,0 +1,146 @@ +/***************************************************************************** + + 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. + + *****************************************************************************/ + +/***************************************************************************** + + multiple_modules.cpp -- Verify two independent SC_ALLOW_CONCURRENCY + modules each feeding the same consumer in the parent simcontext. + Each producer lives in its own child simcontext (its own std::thread). + Both signals are owned by the parent, so each producer's write() + crosses a simcontext boundary. + + Pass criteria: + * Consumer observes exactly N_WRITES values on each signal. + * Each stream is monotonic. + + Exits 0 on pass, 1 on fail. + + *****************************************************************************/ + +#include +#include "sysc/utils/sc_concurrent.h" + +#include + +constexpr int N_WRITES = 4; + +SC_MODULE(producer) +{ + sc_out out; + int m_base; + sc_core::sc_time m_period; + + void run() + { + for (int i = 1; i <= N_WRITES; ++i) { + wait(m_period); + out.write(m_base + i); + } + } + + SC_HAS_PROCESS(producer); + producer(sc_module_name n, int base, sc_core::sc_time period) + : sc_module(n), m_base(base), m_period(period) + { + SC_THREAD(run); + } +}; + +SC_MODULE(consumer) +{ + sc_in in_a; + sc_in in_b; + + int m_count_a = 0; + int m_count_b = 0; + int m_last_a = 0; + int m_last_b = 0; + bool m_mono_a = true; + bool m_mono_b = true; + + void on_a() + { + int v = in_a.read(); + if (v <= m_last_a) m_mono_a = false; + m_last_a = v; + ++m_count_a; + maybe_stop(); + } + void on_b() + { + int v = in_b.read(); + if (v <= m_last_b) m_mono_b = false; + m_last_b = v; + ++m_count_b; + maybe_stop(); + } + + void maybe_stop() + { + if (m_count_a >= N_WRITES && m_count_b >= N_WRITES) + sc_stop(); + } + + SC_CTOR(consumer) + { + SC_METHOD(on_a); sensitive << in_a; dont_initialize(); + SC_METHOD(on_b); sensitive << in_b; dont_initialize(); + } +}; + +int sc_main(int, char*[]) +{ + tlm_utils::tlm_quantumkeeper::set_global_quantum( + sc_core::sc_time(1, sc_core::SC_MS)); + + sc_signal sig_a; + sc_signal sig_b; + + // Two producers: each in its OWN child simcontext; bases are + // disjoint (100+ and 200+) so we can check the streams independently. + SC_ALLOW_CONCURRENCY(producer, sc_core::sc_sync_policy_tlm_quantum) + prod_a("prod_a", 100, sc_core::sc_time(10, sc_core::SC_MS)); + SC_ALLOW_CONCURRENCY(producer, sc_core::sc_sync_policy_tlm_quantum) + prod_b("prod_b", 200, sc_core::sc_time(7, sc_core::SC_MS)); + + consumer cons("cons"); + + prod_a.out(sig_a); + prod_b.out(sig_b); + cons.in_a(sig_a); + cons.in_b(sig_b); + + sc_start(); + + bool ok = + cons.m_count_a == N_WRITES && + cons.m_count_b == N_WRITES && + cons.m_last_a == 100 + N_WRITES && + cons.m_last_b == 200 + N_WRITES && + cons.m_mono_a && cons.m_mono_b; + + std::cout << "a:count=" << cons.m_count_a << " last=" << cons.m_last_a + << " mono=" << (cons.m_mono_a ? "yes" : "no") + << " b:count=" << cons.m_count_b << " last=" << cons.m_last_b + << " mono=" << (cons.m_mono_b ? "yes" : "no") + << " result=" << (ok ? "PASS" : "FAIL") + << std::endl; + + return ok ? 0 : 1; +}