verilator/include/verilated_threading_advisor.h

182 lines
6.7 KiB
C++

// -*- 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 <cstdint>
#include <cstdio>
#include <fstream>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
//=============================================================================
// 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<CpuInfo> 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<int> sockets;
std::set<std::pair<int, int>> physCores; // (socket, core) pairs
for (unsigned cpu = 0; cpu < maxCpus; ++cpu) {
CpuInfo& info = m_cpuInfo[cpu];
info.cpuId = static_cast<int>(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<unsigned>(sockets.size());
m_physicalCores = static_cast<unsigned>(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_