2021-09-27 02:51:11 +00:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
//=============================================================================
|
|
|
|
//
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
//
|
2024-01-01 08:19:59 +00:00
|
|
|
// Copyright 2012-2024 by Wilson Snyder. This program is free software; you
|
2021-09-27 02:51:11 +00:00
|
|
|
// 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
|
2022-03-25 19:46:50 +00:00
|
|
|
/// \brief Verilated run-time profiling header
|
2021-09-27 02:51:11 +00:00
|
|
|
///
|
|
|
|
/// This file is not part of the Verilated public-facing API.
|
|
|
|
/// It is only for internal use by Verilated library routines.
|
|
|
|
///
|
|
|
|
//=============================================================================
|
|
|
|
|
|
|
|
#ifndef VERILATOR_VERILATED_PROFILER_H_
|
|
|
|
#define VERILATOR_VERILATED_PROFILER_H_
|
|
|
|
|
|
|
|
#include "verilatedos.h"
|
2022-08-05 09:56:57 +00:00
|
|
|
|
2022-03-25 19:46:50 +00:00
|
|
|
#include "verilated.h"
|
|
|
|
|
|
|
|
#include <array>
|
|
|
|
#include <atomic>
|
|
|
|
#include <cassert>
|
2022-01-09 21:49:38 +00:00
|
|
|
#include <string>
|
2022-03-25 19:46:50 +00:00
|
|
|
#include <type_traits>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
class VlExecutionProfiler;
|
2022-07-12 10:41:15 +00:00
|
|
|
class VlThreadPool;
|
2022-03-25 19:46:50 +00:00
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
// Macros to simplify generated code
|
|
|
|
|
|
|
|
#define VL_EXEC_TRACE_ADD_RECORD(vlSymsp) \
|
2022-07-12 10:41:15 +00:00
|
|
|
if (VL_UNLIKELY((vlSymsp)->__Vm_executionProfilerp->enabled())) \
|
|
|
|
(vlSymsp)->__Vm_executionProfilerp->addRecord()
|
2022-03-25 19:46:50 +00:00
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
// Return high-precision counter for profiling, or 0x0 if not available
|
2022-09-16 12:17:38 +00:00
|
|
|
VL_ATTR_ALWINLINE QData VL_CPU_TICK() {
|
2022-03-27 19:27:40 +00:00
|
|
|
uint64_t val;
|
2022-03-25 19:46:50 +00:00
|
|
|
VL_GET_CPU_TICK(val);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
// Private class used by VlExecutionProfiler
|
|
|
|
|
|
|
|
#define _VL_FOREACH_APPLY(macro, arg) macro(arg, #arg)
|
|
|
|
|
|
|
|
// clang-format off
|
|
|
|
#define FOREACH_VlExecutionRecord_TYPE(macro) \
|
2023-05-10 11:12:34 +00:00
|
|
|
_VL_FOREACH_APPLY(macro, SECTION_PUSH) \
|
|
|
|
_VL_FOREACH_APPLY(macro, SECTION_POP) \
|
2022-03-25 19:46:50 +00:00
|
|
|
_VL_FOREACH_APPLY(macro, MTASK_BEGIN) \
|
2023-05-10 11:12:34 +00:00
|
|
|
_VL_FOREACH_APPLY(macro, MTASK_END) \
|
|
|
|
_VL_FOREACH_APPLY(macro, EXEC_GRAPH_BEGIN) \
|
|
|
|
_VL_FOREACH_APPLY(macro, EXEC_GRAPH_END)
|
2022-03-25 19:46:50 +00:00
|
|
|
// clang-format on
|
|
|
|
|
|
|
|
class VlExecutionRecord final {
|
|
|
|
friend class VlExecutionProfiler;
|
|
|
|
|
|
|
|
// TYPES
|
|
|
|
enum class Type : uint8_t {
|
|
|
|
#define VL_FOREACH_MACRO(id, name) id,
|
|
|
|
FOREACH_VlExecutionRecord_TYPE(VL_FOREACH_MACRO)
|
|
|
|
#undef VL_FOREACH_MACRO
|
|
|
|
};
|
|
|
|
|
|
|
|
static constexpr const char* const s_ascii[] = {
|
|
|
|
#define VL_FOREACH_MACRO(id, name) name,
|
|
|
|
FOREACH_VlExecutionRecord_TYPE(VL_FOREACH_MACRO)
|
|
|
|
#undef VL_FOREACH_MACRO
|
|
|
|
};
|
|
|
|
|
|
|
|
union Payload {
|
2023-05-10 11:12:34 +00:00
|
|
|
struct {
|
|
|
|
const char* m_name; // Name of section being entered
|
|
|
|
} sectionPush;
|
2022-03-25 19:46:50 +00:00
|
|
|
struct {
|
2022-03-27 19:27:40 +00:00
|
|
|
uint32_t m_id; // MTask id
|
|
|
|
uint32_t m_predictStart; // Time scheduler predicted would start
|
|
|
|
uint32_t m_cpu; // Executing CPU id
|
2022-03-25 19:46:50 +00:00
|
|
|
} mtaskBegin;
|
|
|
|
struct {
|
2022-03-27 19:27:40 +00:00
|
|
|
uint32_t m_id; // MTask id
|
|
|
|
uint32_t m_predictCost; // How long scheduler predicted would take
|
2022-03-25 19:46:50 +00:00
|
|
|
} mtaskEnd;
|
|
|
|
};
|
|
|
|
|
|
|
|
// STATE
|
|
|
|
// Layout below allows efficient packing.
|
2022-03-27 19:27:40 +00:00
|
|
|
const uint64_t m_tick = VL_CPU_TICK(); // Tick at construction
|
2022-03-25 19:46:50 +00:00
|
|
|
Payload m_payload; // The record payload
|
|
|
|
Type m_type; // The record type
|
2022-03-27 19:27:40 +00:00
|
|
|
static_assert(alignof(uint64_t) >= alignof(Payload), "Padding not allowed");
|
2022-03-25 19:46:50 +00:00
|
|
|
static_assert(alignof(Payload) >= alignof(Type), "Padding not allowed");
|
|
|
|
|
2022-03-27 19:27:40 +00:00
|
|
|
static uint16_t getcpu(); // Return currently executing CPU id
|
2022-03-25 19:46:50 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
// CONSTRUCTOR
|
|
|
|
VlExecutionRecord() = default;
|
|
|
|
|
|
|
|
// METHODS
|
2023-05-10 11:12:34 +00:00
|
|
|
void sectionPush(const char* name) {
|
|
|
|
m_payload.sectionPush.m_name = name;
|
|
|
|
m_type = Type::SECTION_PUSH;
|
|
|
|
}
|
|
|
|
void sectionPop() { m_type = Type::SECTION_POP; }
|
2022-03-27 19:27:40 +00:00
|
|
|
void mtaskBegin(uint32_t id, uint32_t predictStart) {
|
2022-03-25 19:46:50 +00:00
|
|
|
m_payload.mtaskBegin.m_id = id;
|
|
|
|
m_payload.mtaskBegin.m_predictStart = predictStart;
|
|
|
|
m_payload.mtaskBegin.m_cpu = getcpu();
|
|
|
|
m_type = Type::MTASK_BEGIN;
|
|
|
|
}
|
2022-03-27 19:27:40 +00:00
|
|
|
void mtaskEnd(uint32_t id, uint32_t predictCost) {
|
2022-03-25 19:46:50 +00:00
|
|
|
m_payload.mtaskEnd.m_id = id;
|
|
|
|
m_payload.mtaskEnd.m_predictCost = predictCost;
|
|
|
|
m_type = Type::MTASK_END;
|
|
|
|
}
|
2023-05-10 11:12:34 +00:00
|
|
|
void execGraphBegin() { m_type = Type::EXEC_GRAPH_BEGIN; }
|
|
|
|
void execGraphEnd() { m_type = Type::EXEC_GRAPH_END; }
|
2022-03-25 19:46:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static_assert(std::is_trivially_destructible<VlExecutionRecord>::value,
|
|
|
|
"VlExecutionRecord should be trivially destructible for fast buffer clearing");
|
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
// VlExecutionProfiler is for collecting profiling data about model execution
|
|
|
|
|
2022-07-12 10:41:15 +00:00
|
|
|
class VlExecutionProfiler final : public VerilatedVirtualBase {
|
2022-03-25 19:46:50 +00:00
|
|
|
// CONSTANTS
|
|
|
|
|
|
|
|
// In order to try to avoid dynamic memory allocations during the actual profiling phase,
|
|
|
|
// trace buffers are pre-allocated to be able to hold [a multiple] of this many records.
|
|
|
|
static constexpr size_t RESERVED_TRACE_CAPACITY = 4096;
|
|
|
|
|
|
|
|
// TYPES
|
|
|
|
|
|
|
|
// Execution traces are recorded into thread local vectors. We can append records of profiling
|
|
|
|
// events to this vector with very low overhead, and then dump them out later. This prevents
|
|
|
|
// the overhead of printf/malloc/IO from corrupting the profiling data. It's super cheap to
|
|
|
|
// append a VlProfileRec struct on the end of a pre-allocated vector; this is the only cost we
|
|
|
|
// pay in real-time during a profiling cycle. Internal note: Globals may multi-construct, see
|
|
|
|
// verilated.cpp top.
|
|
|
|
using ExecutionTrace = std::vector<VlExecutionRecord>;
|
|
|
|
|
|
|
|
// STATE
|
2022-07-12 10:41:15 +00:00
|
|
|
VerilatedContext& m_context; // The context this profiler is under
|
2022-11-05 12:47:34 +00:00
|
|
|
static thread_local ExecutionTrace t_trace; // thread-local trace buffers
|
2022-05-13 11:21:39 +00:00
|
|
|
mutable VerilatedMutex m_mutex;
|
2022-03-25 19:46:50 +00:00
|
|
|
// Map from thread id to &t_trace of given thread
|
|
|
|
std::map<uint32_t, ExecutionTrace*> m_traceps VL_GUARDED_BY(m_mutex);
|
|
|
|
|
|
|
|
bool m_enabled = false; // Is profiling currently enabled
|
|
|
|
|
2022-03-27 19:27:40 +00:00
|
|
|
uint64_t m_tickBegin = 0; // Sample time (rdtsc() on x86) at beginning of collection
|
|
|
|
uint64_t m_lastStartReq = 0; // Last requested profiling start (in simulation time)
|
|
|
|
uint32_t m_windowCount = 0; // Track our position in the cache warmup and profile window
|
2022-01-09 21:49:38 +00:00
|
|
|
|
2021-09-27 02:51:11 +00:00
|
|
|
public:
|
2022-03-25 19:46:50 +00:00
|
|
|
// CONSTRUCTOR
|
2022-07-12 10:41:15 +00:00
|
|
|
explicit VlExecutionProfiler(VerilatedContext& context);
|
2022-07-30 15:52:35 +00:00
|
|
|
~VlExecutionProfiler() override = default;
|
2022-03-25 19:46:50 +00:00
|
|
|
|
2021-09-27 02:51:11 +00:00
|
|
|
// METHODS
|
2022-03-25 19:46:50 +00:00
|
|
|
|
|
|
|
// Is profiling enabled
|
2022-09-16 12:17:38 +00:00
|
|
|
bool enabled() const { return m_enabled; }
|
2022-03-25 19:46:50 +00:00
|
|
|
// Append a trace record to the trace buffer of the current thread
|
2022-09-16 12:17:38 +00:00
|
|
|
static VlExecutionRecord& addRecord() {
|
2022-03-25 19:46:50 +00:00
|
|
|
t_trace.emplace_back();
|
|
|
|
return t_trace.back();
|
|
|
|
}
|
|
|
|
// Configure profiler (called in beginning of 'eval')
|
2022-07-12 10:41:15 +00:00
|
|
|
void configure();
|
2022-03-25 19:46:50 +00:00
|
|
|
// Setup profiling on a particular thread;
|
|
|
|
void setupThread(uint32_t threadId);
|
|
|
|
// Clear all profiling data
|
|
|
|
void clear() VL_MT_SAFE_EXCLUDES(m_mutex);
|
|
|
|
// Write profiling data into file
|
2022-03-27 19:27:40 +00:00
|
|
|
void dump(const char* filenamep, uint64_t tickEnd) VL_MT_SAFE_EXCLUDES(m_mutex);
|
2022-06-23 00:06:23 +00:00
|
|
|
|
2022-07-12 10:41:15 +00:00
|
|
|
// Passed to VerilatedContext to create the VlExecutionProfiler profiler instance
|
|
|
|
static VerilatedVirtualBase* construct(VerilatedContext& context);
|
2021-09-27 02:51:11 +00:00
|
|
|
};
|
|
|
|
|
2022-03-25 19:46:50 +00:00
|
|
|
//=============================================================================
|
|
|
|
// VlPgoProfiler is for collecting profiling data for PGO
|
|
|
|
|
2024-11-30 01:20:38 +00:00
|
|
|
template <std::size_t N_Entries>
|
2022-08-05 09:56:57 +00:00
|
|
|
class VlPgoProfiler final {
|
2022-03-25 19:46:50 +00:00
|
|
|
// TYPES
|
|
|
|
struct Record final {
|
|
|
|
const std::string m_name; // Hashed name of mtask/etc
|
|
|
|
const size_t m_counterNumber = 0; // Which counter has data
|
|
|
|
};
|
|
|
|
|
|
|
|
// Counters are stored packed, all together to reduce cache effects
|
2024-11-30 01:20:38 +00:00
|
|
|
std::array<uint64_t, N_Entries> m_counters; // Time spent on this record
|
2022-03-25 19:46:50 +00:00
|
|
|
std::vector<Record> m_records; // Record information
|
2021-09-27 02:51:11 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
// METHODS
|
2022-03-25 19:46:50 +00:00
|
|
|
VlPgoProfiler() = default;
|
|
|
|
~VlPgoProfiler() = default;
|
2021-09-27 02:51:11 +00:00
|
|
|
void write(const char* modelp, const std::string& filename) VL_MT_SAFE;
|
|
|
|
void addCounter(size_t counter, const std::string& name) {
|
2024-11-30 01:20:38 +00:00
|
|
|
VL_DEBUG_IF(assert(counter < N_Entries););
|
2022-03-25 19:46:50 +00:00
|
|
|
m_records.emplace_back(Record{name, counter});
|
2021-09-27 02:51:11 +00:00
|
|
|
}
|
|
|
|
void startCounter(size_t counter) {
|
2022-03-25 19:46:50 +00:00
|
|
|
// -= so when we add end time in stopCounter, the net effect is adding the difference,
|
|
|
|
// without needing to hold onto a temporary
|
|
|
|
m_counters[counter] -= VL_CPU_TICK();
|
2021-09-27 02:51:11 +00:00
|
|
|
}
|
2022-03-25 19:46:50 +00:00
|
|
|
void stopCounter(size_t counter) { m_counters[counter] += VL_CPU_TICK(); }
|
2021-09-27 02:51:11 +00:00
|
|
|
};
|
|
|
|
|
2024-11-30 01:20:38 +00:00
|
|
|
template <std::size_t N_Entries>
|
|
|
|
void VlPgoProfiler<N_Entries>::write(const char* modelp, const std::string& filename) VL_MT_SAFE {
|
2021-09-27 02:51:11 +00:00
|
|
|
static VerilatedMutex s_mutex;
|
|
|
|
const VerilatedLockGuard lock{s_mutex};
|
|
|
|
|
|
|
|
// On the first call we create the file. On later calls we append.
|
|
|
|
// So when we have multiple models in an executable, possibly even
|
|
|
|
// running on different threads, each will have a different symtab so
|
|
|
|
// each will collect is own data correctly. However when each is
|
2022-05-15 18:26:55 +00:00
|
|
|
// destroyed we need to get all the data, not keep overwriting and only
|
2021-09-27 02:51:11 +00:00
|
|
|
// get the last model's data.
|
|
|
|
static bool s_firstCall = true;
|
|
|
|
|
|
|
|
VL_DEBUG_IF(VL_DBG_MSGF("+prof+vlt+file writing to '%s'\n", filename.c_str()););
|
|
|
|
|
2022-03-25 19:46:50 +00:00
|
|
|
FILE* const fp = std::fopen(filename.c_str(), s_firstCall ? "w" : "a");
|
2021-09-27 02:51:11 +00:00
|
|
|
if (VL_UNLIKELY(!fp)) {
|
|
|
|
VL_FATAL_MT(filename.c_str(), 0, "", "+prof+vlt+file file not writable");
|
|
|
|
}
|
|
|
|
s_firstCall = false;
|
|
|
|
|
|
|
|
// TODO Perhaps merge with verilated_coverage output format, so can
|
|
|
|
// have a common merging and reporting tool, etc.
|
|
|
|
fprintf(fp, "// Verilated model profile-guided optimization data dump file\n");
|
|
|
|
fprintf(fp, "`verilator_config\n");
|
|
|
|
|
2022-03-25 19:46:50 +00:00
|
|
|
for (const Record& rec : m_records) {
|
2022-01-01 21:04:20 +00:00
|
|
|
fprintf(fp, "profile_data -model \"%s\" -mtask \"%s\" -cost 64'd%" PRIu64 "\n", modelp,
|
2022-03-25 19:46:50 +00:00
|
|
|
rec.m_name.c_str(), m_counters[rec.m_counterNumber]);
|
2021-09-27 02:51:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::fclose(fp);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|