2018-07-23 00:54:28 +00:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
//=============================================================================
|
|
|
|
//
|
2021-03-20 21:46:00 +00:00
|
|
|
// Code available from: https://verilator.org
|
|
|
|
//
|
2022-01-01 13:26:40 +00:00
|
|
|
// Copyright 2012-2022 by Wilson Snyder. This program is free software; you can
|
2020-03-21 15:24:24 +00:00
|
|
|
// 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
|
2018-07-23 00:54:28 +00:00
|
|
|
//
|
|
|
|
//=============================================================================
|
|
|
|
///
|
|
|
|
/// \file
|
2021-03-20 21:46:00 +00:00
|
|
|
/// \brief Verilated thread pool implementation code
|
|
|
|
///
|
|
|
|
/// This file must be compiled and linked against all Verilated objects
|
|
|
|
/// that use --threads.
|
|
|
|
///
|
|
|
|
/// Use "verilator --threads" to add this to the Makefile for the linker.
|
2018-07-23 00:54:28 +00:00
|
|
|
///
|
|
|
|
//=============================================================================
|
|
|
|
|
|
|
|
#include "verilatedos.h"
|
|
|
|
#include "verilated_threads.h"
|
2018-10-14 17:43:24 +00:00
|
|
|
|
2018-07-23 00:54:28 +00:00
|
|
|
#include <cstdio>
|
2021-09-05 15:56:28 +00:00
|
|
|
#include <fstream>
|
2018-07-23 00:54:28 +00:00
|
|
|
|
2021-03-05 00:23:40 +00:00
|
|
|
//=============================================================================
|
|
|
|
// Globals
|
|
|
|
|
|
|
|
// Internal note: Globals may multi-construct, see verilated.cpp top.
|
|
|
|
|
2019-10-07 23:27:31 +00:00
|
|
|
std::atomic<vluint64_t> VlMTaskVertex::s_yields;
|
2018-07-23 00:54:28 +00:00
|
|
|
|
2020-08-15 14:12:55 +00:00
|
|
|
VL_THREAD_LOCAL VlThreadPool::ProfileTrace* VlThreadPool::t_profilep = nullptr;
|
2018-07-23 00:54:28 +00:00
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
// VlMTaskVertex
|
|
|
|
|
|
|
|
VlMTaskVertex::VlMTaskVertex(vluint32_t upstreamDepCount)
|
2020-08-16 13:55:36 +00:00
|
|
|
: m_upstreamDepsDone{0}
|
|
|
|
, m_upstreamDepCount{upstreamDepCount} {
|
2018-07-23 00:54:28 +00:00
|
|
|
assert(atomic_is_lock_free(&m_upstreamDepsDone));
|
|
|
|
}
|
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
// VlWorkerThread
|
|
|
|
|
2021-03-07 16:01:54 +00:00
|
|
|
VlWorkerThread::VlWorkerThread(VlThreadPool* poolp, VerilatedContext* contextp, bool profiling)
|
2021-07-25 17:38:27 +00:00
|
|
|
: m_ready_size{0}
|
|
|
|
, m_poolp{poolp}
|
2020-08-16 13:55:36 +00:00
|
|
|
, m_profiling{profiling} // Must init this last -- after setting up fields that it might read:
|
|
|
|
, m_exiting{false}
|
2021-03-07 16:01:54 +00:00
|
|
|
, m_cthread{startWorker, this}
|
|
|
|
, m_contextp{contextp} {}
|
2018-07-23 00:54:28 +00:00
|
|
|
|
|
|
|
VlWorkerThread::~VlWorkerThread() {
|
|
|
|
m_exiting.store(true, std::memory_order_release);
|
2019-10-07 23:27:31 +00:00
|
|
|
wakeUp();
|
2018-07-23 00:54:28 +00:00
|
|
|
// The thread should exit; join it.
|
|
|
|
m_cthread.join();
|
|
|
|
}
|
|
|
|
|
|
|
|
void VlWorkerThread::workerLoop() {
|
2020-04-04 17:45:24 +00:00
|
|
|
if (VL_UNLIKELY(m_profiling)) m_poolp->setupProfilingClientThread();
|
2018-07-23 00:54:28 +00:00
|
|
|
|
|
|
|
ExecRec work;
|
2020-08-15 14:12:55 +00:00
|
|
|
work.m_fnp = nullptr;
|
2018-07-23 00:54:28 +00:00
|
|
|
|
2020-04-04 02:31:54 +00:00
|
|
|
while (true) {
|
2020-04-04 17:45:24 +00:00
|
|
|
if (VL_LIKELY(!work.m_fnp)) dequeWork(&work);
|
2018-07-23 00:54:28 +00:00
|
|
|
|
|
|
|
// Do this here, not above, to avoid a race with the destructor.
|
2020-04-14 02:51:35 +00:00
|
|
|
if (VL_UNLIKELY(m_exiting.load(std::memory_order_acquire))) break;
|
2018-07-23 00:54:28 +00:00
|
|
|
|
|
|
|
if (VL_LIKELY(work.m_fnp)) {
|
2021-06-13 13:33:11 +00:00
|
|
|
work.m_fnp(work.m_selfp, work.m_evenCycle);
|
2020-08-15 14:12:55 +00:00
|
|
|
work.m_fnp = nullptr;
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-04 17:45:24 +00:00
|
|
|
if (VL_UNLIKELY(m_profiling)) m_poolp->tearDownProfilingClientThread();
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
|
2021-03-07 16:01:54 +00:00
|
|
|
void VlWorkerThread::startWorker(VlWorkerThread* workerp) {
|
|
|
|
Verilated::threadContextp(workerp->m_contextp);
|
|
|
|
workerp->workerLoop();
|
|
|
|
}
|
2018-07-23 00:54:28 +00:00
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
// VlThreadPool
|
|
|
|
|
2021-03-07 16:01:54 +00:00
|
|
|
VlThreadPool::VlThreadPool(VerilatedContext* contextp, int nThreads, bool profiling)
|
2020-08-16 13:55:36 +00:00
|
|
|
: m_profiling{profiling} {
|
2018-07-23 00:54:28 +00:00
|
|
|
// --threads N passes nThreads=N-1, as the "main" threads counts as 1
|
2021-06-19 02:19:35 +00:00
|
|
|
const unsigned cpus = std::thread::hardware_concurrency();
|
2020-04-04 17:45:24 +00:00
|
|
|
if (cpus < nThreads + 1) {
|
2019-06-15 13:17:51 +00:00
|
|
|
static int warnedOnce = 0;
|
|
|
|
if (!warnedOnce++) {
|
|
|
|
VL_PRINTF_MT("%%Warning: System has %u CPUs but model Verilated with"
|
2020-04-04 17:45:24 +00:00
|
|
|
" --threads %d; may run slow.\n",
|
|
|
|
cpus, nThreads + 1);
|
2019-06-15 13:17:51 +00:00
|
|
|
}
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
// Create'em
|
2020-04-04 17:45:24 +00:00
|
|
|
for (int i = 0; i < nThreads; ++i) {
|
2021-07-24 12:36:11 +00:00
|
|
|
m_workers.push_back(new VlWorkerThread{this, contextp, profiling});
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
// Set up a profile buffer for the current thread too -- on the
|
|
|
|
// assumption that it's the same thread that calls eval and may be
|
|
|
|
// donated to run mtasks during the eval.
|
2020-04-04 17:45:24 +00:00
|
|
|
if (VL_UNLIKELY(m_profiling)) setupProfilingClientThread();
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
VlThreadPool::~VlThreadPool() {
|
2020-11-11 02:40:14 +00:00
|
|
|
// Each ~WorkerThread will wait for its thread to exit.
|
|
|
|
for (auto& i : m_workers) delete i;
|
2020-04-04 17:45:24 +00:00
|
|
|
if (VL_UNLIKELY(m_profiling)) tearDownProfilingClientThread();
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void VlThreadPool::tearDownProfilingClientThread() {
|
|
|
|
assert(t_profilep);
|
|
|
|
delete t_profilep;
|
2020-08-15 14:12:55 +00:00
|
|
|
t_profilep = nullptr;
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
|
2021-03-06 23:29:11 +00:00
|
|
|
void VlThreadPool::setupProfilingClientThread() VL_MT_SAFE_EXCLUDES(m_mutex) {
|
2018-07-23 00:54:28 +00:00
|
|
|
assert(!t_profilep);
|
|
|
|
t_profilep = new ProfileTrace;
|
|
|
|
// Reserve some space in the thread-local profiling buffer;
|
|
|
|
// try not to malloc while collecting profiling.
|
|
|
|
t_profilep->reserve(4096);
|
|
|
|
{
|
2021-07-24 12:36:11 +00:00
|
|
|
const VerilatedLockGuard lock{m_mutex};
|
2018-07-23 00:54:28 +00:00
|
|
|
m_allProfiles.insert(t_profilep);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-06 23:29:11 +00:00
|
|
|
void VlThreadPool::profileAppendAll(const VlProfileRec& rec) VL_MT_SAFE_EXCLUDES(m_mutex) {
|
2021-07-24 12:36:11 +00:00
|
|
|
const VerilatedLockGuard lock{m_mutex};
|
2020-08-16 15:43:49 +00:00
|
|
|
for (const auto& profilep : m_allProfiles) {
|
2018-07-23 00:54:28 +00:00
|
|
|
// Every thread's profile trace gets a copy of rec.
|
2020-08-16 15:43:49 +00:00
|
|
|
profilep->emplace_back(rec);
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-23 23:43:02 +00:00
|
|
|
void VlThreadPool::profileDump(const char* filenamep, vluint64_t tickStart, vluint64_t tickEnd)
|
2021-03-06 23:29:11 +00:00
|
|
|
VL_MT_SAFE_EXCLUDES(m_mutex) {
|
2021-07-24 12:36:11 +00:00
|
|
|
const VerilatedLockGuard lock{m_mutex};
|
2018-07-23 00:54:28 +00:00
|
|
|
VL_DEBUG_IF(VL_DBG_MSGF("+prof+threads writing to '%s'\n", filenamep););
|
|
|
|
|
2021-06-19 02:19:35 +00:00
|
|
|
FILE* const fp = std::fopen(filenamep, "w");
|
2018-07-23 00:54:28 +00:00
|
|
|
if (VL_UNLIKELY(!fp)) {
|
|
|
|
VL_FATAL_MT(filenamep, 0, "", "+prof+threads+file file not writable");
|
2020-04-05 22:30:46 +00:00
|
|
|
// cppcheck-suppress resourceLeak // bug, doesn't realize fp is nullptr
|
2020-09-19 01:27:36 +00:00
|
|
|
return; // LCOV_EXCL_LINE
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO Perhaps merge with verilated_coverage output format, so can
|
|
|
|
// have a common merging and reporting tool, etc.
|
2021-09-24 02:59:36 +00:00
|
|
|
fprintf(fp, "VLPROFTHREAD 1.1 # Verilator thread profile dump version 1.1\n");
|
2022-01-01 21:04:20 +00:00
|
|
|
fprintf(fp, "VLPROF arg --threads %" PRIu64 "\n", vluint64_t(m_workers.size() + 1));
|
|
|
|
fprintf(fp, "VLPROF arg +verilator+prof+threads+start+%" PRIu64 "\n",
|
2021-03-07 16:01:54 +00:00
|
|
|
Verilated::threadContextp()->profThreadsStart());
|
|
|
|
fprintf(fp, "VLPROF arg +verilator+prof+threads+window+%u\n",
|
|
|
|
Verilated::threadContextp()->profThreadsWindow());
|
2022-01-01 21:04:20 +00:00
|
|
|
fprintf(fp, "VLPROF stat yields %" PRIu64 "\n", VlMTaskVertex::yields());
|
2018-07-23 00:54:28 +00:00
|
|
|
|
2021-09-05 15:56:28 +00:00
|
|
|
// Copy /proc/cpuinfo into this output so verilator_gantt can be run on
|
|
|
|
// a different machine
|
|
|
|
{
|
|
|
|
const std::unique_ptr<std::ifstream> ifp{new std::ifstream("/proc/cpuinfo")};
|
|
|
|
if (!ifp->fail()) {
|
|
|
|
std::string line;
|
|
|
|
while (std::getline(*ifp, line)) { fprintf(fp, "VLPROFPROC %s\n", line.c_str()); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-23 00:54:28 +00:00
|
|
|
vluint32_t thread_id = 0;
|
2020-08-16 15:43:49 +00:00
|
|
|
for (const auto& pi : m_allProfiles) {
|
2018-07-23 00:54:28 +00:00
|
|
|
++thread_id;
|
|
|
|
|
|
|
|
bool printing = false; // False while in warmup phase
|
2020-08-16 15:43:49 +00:00
|
|
|
for (const auto& ei : *pi) {
|
|
|
|
switch (ei.m_type) {
|
2020-04-14 02:51:35 +00:00
|
|
|
case VlProfileRec::TYPE_BARRIER: //
|
2018-07-23 00:54:28 +00:00
|
|
|
printing = true;
|
|
|
|
break;
|
2021-09-24 02:59:36 +00:00
|
|
|
case VlProfileRec::TYPE_EVAL:
|
|
|
|
if (!printing) break;
|
|
|
|
fprintf(fp,
|
2022-01-01 21:04:20 +00:00
|
|
|
"VLPROF eval start %" PRIu64 " elapsed %" PRIu64 " cpu %u on thread %u\n",
|
2021-09-24 02:59:36 +00:00
|
|
|
ei.m_startTime - tickStart, (ei.m_endTime - ei.m_startTime), ei.m_cpu,
|
|
|
|
thread_id);
|
|
|
|
break;
|
|
|
|
case VlProfileRec::TYPE_EVAL_LOOP:
|
|
|
|
if (!printing) break;
|
|
|
|
fprintf(fp,
|
2022-01-01 21:04:20 +00:00
|
|
|
"VLPROF eval_loop start %" PRIu64 " elapsed %" PRIu64
|
2021-09-24 02:59:36 +00:00
|
|
|
" cpu %u on thread %u\n",
|
|
|
|
ei.m_startTime - tickStart, (ei.m_endTime - ei.m_startTime), ei.m_cpu,
|
|
|
|
thread_id);
|
|
|
|
break;
|
2018-07-23 00:54:28 +00:00
|
|
|
case VlProfileRec::TYPE_MTASK_RUN:
|
|
|
|
if (!printing) break;
|
2020-04-04 17:45:24 +00:00
|
|
|
fprintf(fp,
|
|
|
|
"VLPROF mtask %d"
|
2022-01-01 21:04:20 +00:00
|
|
|
" start %" PRIu64 " elapsed %" PRIu64
|
2021-09-24 02:59:36 +00:00
|
|
|
" predict_start %u predict_cost %u cpu %u on thread %u\n",
|
|
|
|
ei.m_mtaskId, ei.m_startTime - tickStart, (ei.m_endTime - ei.m_startTime),
|
|
|
|
ei.m_predictStart, ei.m_predictCost, ei.m_cpu, thread_id);
|
2018-07-23 00:54:28 +00:00
|
|
|
break;
|
2019-07-01 02:37:03 +00:00
|
|
|
default: assert(false); break; // LCOV_EXCL_LINE
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-01 21:04:20 +00:00
|
|
|
fprintf(fp, "VLPROF stat ticks %" PRIu64 "\n", tickEnd - tickStart);
|
2018-07-23 00:54:28 +00:00
|
|
|
|
2021-03-27 01:23:18 +00:00
|
|
|
std::fclose(fp);
|
2018-07-23 00:54:28 +00:00
|
|
|
}
|