diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 832bbbece..6df1635b9 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -125,6 +125,7 @@ John Wehle Jonathan Drolet Jonathan Schröter Jordan McConnon +Jose Drowne Jose Loyola Josep Sans Joseph Nwabueze diff --git a/include/verilated.cpp b/include/verilated.cpp index c9529811e..7452261db 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -79,6 +79,7 @@ #endif #include "verilated_threads.h" +#include "verilated_threading_advisor.h" // clang-format on #include "verilated_trace.h" @@ -2881,7 +2882,14 @@ void VerilatedContext::addModel(const VerilatedModel* modelp) { VerilatedVirtualBase* VerilatedContext::threadPoolp() { if (m_threads == 1) return nullptr; - if (!m_threadPool) m_threadPool.reset(new VlThreadPool{this, m_threads - 1}); + if (!m_threadPool) { + m_threadPool.reset(new VlThreadPool{this, m_threads - 1}); + // Run threading advisor if enabled via +verilator+threading+advisor + if (threadingAdvisor()) { + static VlThreadingAdvisor advisor; + advisor.analyze(m_threads, quiet()); + } + } return m_threadPool.get(); } @@ -2998,6 +3006,8 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) { profExecFilename(str); } else if (commandArgVlString(arg, "+verilator+prof+vlt+file+", str)) { profVltFilename(str); + } else if (arg == "+verilator+threading+advisor") { + threadingAdvisor(true); } else if (arg == "+verilator+quiet") { quiet(true); } else if (commandArgVlUint64(arg, "+verilator+rand+reset+", u64, 0, 2)) { diff --git a/include/verilated.h b/include/verilated.h index e41f66dfc..ee3b28fe2 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -406,6 +406,7 @@ protected: // Fast path uint64_t m_profExecStart = 1; // +prof+exec+start time uint32_t m_profExecWindow = 2; // +prof+exec+window size + bool m_threadingAdvisor = false; // +threading+advisor enabled // Slow path std::string m_coverageFilename; // +coverage+file filename std::string m_profExecFilename; // +prof+exec+file filename @@ -647,6 +648,10 @@ public: std::string profVltFilename() const VL_MT_SAFE; void profVltFilename(const std::string& flag) VL_MT_SAFE; + // Internal: Threading advisor + bool threadingAdvisor() const VL_MT_SAFE { return m_ns.m_threadingAdvisor; } + void threadingAdvisor(bool flag) VL_MT_SAFE { m_ns.m_threadingAdvisor = flag; } + // Internal: SMT solver program std::string solverProgram() const VL_MT_SAFE; void solverProgram(const std::string& flag) VL_MT_SAFE; diff --git a/include/verilated_threading_advisor.h b/include/verilated_threading_advisor.h new file mode 100644 index 000000000..f2fa289fd --- /dev/null +++ b/include/verilated_threading_advisor.h @@ -0,0 +1,181 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// Copyright 2025 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated runtime threading advisor +/// +/// This file provides runtime analysis of threading configuration and +/// emits warnings when potential issues are detected. Unlike verilator_gantt +/// which requires explicit profiling and post-hoc analysis, this advisor +/// checks for common configuration issues automatically. +/// +/// This file is not part of the Verilated public-facing API. +/// It is only for internal use by Verilated library routines. +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_THREADING_ADVISOR_H_ +#define VERILATOR_VERILATED_THREADING_ADVISOR_H_ + +#include "verilatedos.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +//============================================================================= +// VlThreadingAdvisor - Runtime threading configuration advisor +// +// Analyzes CPU topology and threading configuration to detect potential +// performance issues. This is advisory only - it does not affect simulation. + +class VlThreadingAdvisor final { + // TYPES + struct CpuInfo final { + int cpuId = -1; // Logical CPU ID + int physicalId = -1; // Physical socket/package ID + int coreId = -1; // Core ID within socket + bool valid = false; // True if info was successfully read + }; + + // MEMBERS + std::vector m_cpuInfo; // CPU topology information + unsigned m_physicalCores = 0; // Number of physical cores + unsigned m_logicalCpus = 0; // Number of logical CPUs (hw threads) + unsigned m_sockets = 0; // Number of CPU sockets + bool m_hyperthreading = false; // True if SMT/hyperthreading detected + bool m_topologyRead = false; // True if topology was successfully read + +public: + // CONSTRUCTORS + VlThreadingAdvisor() { readTopology(); } + ~VlThreadingAdvisor() = default; + + // METHODS + + /// Analyze threading configuration and emit warnings if issues detected + /// @param nthreads Number of simulation threads (including main thread) + /// @param quiet If true, suppress all output + void analyze(unsigned nthreads, bool quiet) const { + if (quiet) return; + if (nthreads <= 1) return; // Single-threaded, nothing to check + if (!m_topologyRead) return; // Can't analyze without topology + + checkThreadCount(nthreads); + checkHyperthreading(nthreads); + } + + // ACCESSORS + unsigned physicalCores() const { return m_physicalCores; } + unsigned logicalCpus() const { return m_logicalCpus; } + unsigned sockets() const { return m_sockets; } + bool hasHyperthreading() const { return m_hyperthreading; } + +private: + void readTopology() { +#if defined(__linux__) + readTopologyLinux(); +#elif defined(__APPLE__) + readTopologyMacOS(); +#endif + } + +#if defined(__linux__) + void readTopologyLinux() { + // Read CPU topology from /sys/devices/system/cpu/ + const unsigned maxCpus = VlOs::getProcessDefaultParallelism(); + m_cpuInfo.resize(maxCpus); + + std::set sockets; + std::set> physCores; // (socket, core) pairs + + for (unsigned cpu = 0; cpu < maxCpus; ++cpu) { + CpuInfo& info = m_cpuInfo[cpu]; + info.cpuId = static_cast(cpu); + + // Read physical package ID (socket) + std::ostringstream physPath; + physPath << "/sys/devices/system/cpu/cpu" << cpu + << "/topology/physical_package_id"; + std::ifstream physFile(physPath.str()); + if (physFile.good()) { + physFile >> info.physicalId; + sockets.insert(info.physicalId); + } + + // Read core ID + std::ostringstream corePath; + corePath << "/sys/devices/system/cpu/cpu" << cpu << "/topology/core_id"; + std::ifstream coreFile(corePath.str()); + if (coreFile.good()) { + coreFile >> info.coreId; + if (info.physicalId >= 0) { + physCores.insert({info.physicalId, info.coreId}); + } + } + + info.valid = (info.physicalId >= 0 && info.coreId >= 0); + } + + m_logicalCpus = maxCpus; + m_sockets = static_cast(sockets.size()); + m_physicalCores = static_cast(physCores.size()); + m_hyperthreading = (m_logicalCpus > m_physicalCores); + m_topologyRead = (m_physicalCores > 0); + } +#endif + +#if defined(__APPLE__) + void readTopologyMacOS() { + // On macOS, use sysctl to get CPU info + // For simplicity, just use basic heuristics + m_logicalCpus = VlOs::getProcessDefaultParallelism(); + // Apple Silicon doesn't have traditional hyperthreading + // Intel Macs may have it - assume no HT for simplicity + m_physicalCores = m_logicalCpus; + m_sockets = 1; + m_hyperthreading = false; + m_topologyRead = true; + } +#endif + + void checkThreadCount(unsigned nthreads) const { + // Check if requesting more threads than physical cores + if (m_hyperthreading && nthreads > m_physicalCores) { + std::fprintf(stderr, + "%%Warning: Simulation uses %u threads but system has only %u physical " + "cores (%u logical CPUs with hyperthreading).\n", + nthreads, m_physicalCores, m_logicalCpus); + std::fprintf(stderr, + " : Using more threads than physical cores often reduces " + "performance.\n"); + std::fprintf(stderr, + " : Consider --threads %u or use numactl to bind to physical " + "cores.\n", + m_physicalCores); + } + } + + void checkHyperthreading(unsigned /*nthreads*/) const { + // On systems with hyperthreading, could warn about potential core sharing + // However, this is too noisy for default behavior. Users who want detailed + // analysis should use +verilator+prof+exec and verilator_gantt. + } +}; + +#endif // VERILATOR_VERILATED_THREADING_ADVISOR_H_