Files
verilator/include/verilated_fst_c.cpp
T
Geza Lore c99aa8ede5 Fix erroneous implicit conversions of VlWide (#7642)
Change WDataInP/WDataOutP to be opaque handles types instead of aliases
to raw pointers. This subsequently eliminates needing an implicit cast
operator in VlWide, which is replaced with implicit constructors of
WDataInP/WDataOutP that can create a handle from a VlWide. This
eliminates some unsafe conversions that the previous implicit cast
operator unintentionally enabled (e.g. #7618). It also eliminates
having to insert ".data()" in various places int he generated code, which
simplifies internals (the only place ".data()" should be needed is in
calls to variadic functions where the expected type of the argument is
not WDataInP/WDataOutP).

The handles otherwise behave like pointers, implementing the minimal
amount of operators required to code the runtime. The handle is still
only a single pointer, and will be passed in registers as before, so
this patch should be performance neutral.

As part of this removed WData, which used to be an alias for EData.
All uses are now either EData*, WDataInP, WDataOutP, or VlWide directly.
2026-05-22 20:05:08 +01:00

429 lines
19 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//=============================================================================
//
// Code available from: https://verilator.org
//
// 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-FileCopyrightText: 2001-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//=============================================================================
///
/// \file
/// \brief Verilated C++ tracing in FST format implementation code
///
/// This file must be compiled and linked against all Verilated objects
/// that use --trace-fst.
///
/// Use "verilator --trace-fst" to add this to the Makefile for the linker.
///
//=============================================================================
// clang-format off
#include "verilated.h"
#include "verilated_fst_c.h"
// Include fstcpp cpp file directly
#include "fstcpp/fstcpp_variable_info.cpp"
#include "fstcpp/fstcpp_writer.cpp"
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <sstream>
#include <type_traits>
#include <vector>
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
# include <io.h>
#else
# include <unistd.h>
#endif
// clang-format on
//=============================================================================
// Check that forward declared types matches the FST API types
static_assert(std::is_same<vlFstHandle, fst::Handle>::value, "vlFstHandle mismatch");
static_assert(std::is_same<vlFstEnumHandle, fst::EnumHandle>::value, "vlFstHandle mismatch");
//=============================================================================
// Specialization of the generics for this trace format
#define VL_SUB_T VerilatedFst
#define VL_BUF_T VerilatedFstBuffer
#include "verilated_trace_imp.h"
#undef VL_SUB_T
#undef VL_BUF_T
//=============================================================================
// VerilatedFst
VerilatedFst::VerilatedFst(void* /*fst*/) {}
VerilatedFst::~VerilatedFst() {
if (m_fst) VL_DO_CLEAR(delete m_fst, m_fst = nullptr); // LCOV_EXCL_BR_LINE
if (m_symbolp) VL_DO_CLEAR(delete[] m_symbolp, m_symbolp = nullptr); // LCOV_EXCL_BR_LINE
if (m_strbufp) VL_DO_CLEAR(delete[] m_strbufp, m_strbufp = nullptr); // LCOV_EXCL_BR_LINE
}
void VerilatedFst::open(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex) {
const VerilatedLockGuard lock{m_mutex};
m_fst = new fst::Writer{filename}; // LCOV_EXCL_BR_LINE
m_fst->setWriterPackType(fst::WriterPackType::LZ4);
m_fst->setTimecale(int8_t(round(log10(timeRes()))));
// if (m_useFstWriterThread) fstWriterSetParallelMode(m_fst, 1);
m_fst->setWriter("Generated by VerilatedFst");
constDump(true); // First dump must contain the const signals
fullDump(true); // First dump must be full for fst
Super::traceInit();
// convert m_code2symbol into an array for fast lookup
if (!m_symbolp) {
m_symbolp = new fst::Handle[nextCode()]{0}; // LCOV_EXCL_BR_LINE
for (const auto& i : m_code2symbol) m_symbolp[i.first] = i.second;
}
m_code2symbol.clear();
// Allocate string buffer for arrays
if (!m_strbufp) m_strbufp = new char[maxBits() + 32];
}
void VerilatedFst::close() VL_MT_SAFE_EXCLUDES(m_mutex) {
const VerilatedLockGuard lock{m_mutex};
Super::closeBase();
emitTimeChangeMaybe();
if (m_fst) m_fst->close(); // LCOV_EXCL_BR_LINE
m_fst = nullptr;
}
void VerilatedFst::flush() VL_MT_SAFE_EXCLUDES(m_mutex) {
const VerilatedLockGuard lock{m_mutex};
Super::flushBase();
emitTimeChangeMaybe();
if (m_fst) m_fst->flushValueChangeData(); // LCOV_EXCL_BR_LINE
}
void VerilatedFst::emitTimeChange(uint64_t timeui) {
if (!timeui) m_fst->emitTimeChange(timeui);
m_timeui = timeui;
}
VL_ATTR_ALWINLINE
void VerilatedFst::emitTimeChangeMaybe() {
if (VL_UNLIKELY(m_timeui)) {
m_fst->emitTimeChange(m_timeui);
m_timeui = 0;
}
}
//=============================================================================
// Decl
void VerilatedFst::declDTypeEnum(int dtypenum, const char* name, uint32_t elements,
unsigned int minValbits, const char** itemNamesp,
const char** itemValuesp) {
std::vector<std::pair<const char*, const char*>> itemNameValuesp{elements};
for (uint32_t i = 0; i < elements; ++i) {
itemNameValuesp[i].first = itemNamesp[i];
itemNameValuesp[i].second = itemValuesp[i];
}
const fst::EnumHandle enumNum = m_fst->createEnumTable(name, minValbits, itemNameValuesp);
const bool newEntry = m_local2fstdtype[initUserp()].emplace(dtypenum, enumNum).second;
assert(newEntry);
(void)newEntry; // Prevent unused variable warning when asserts are disabled
}
void VerilatedFst::pushPrefix(const char* namep, VerilatedTracePrefixType type, int left,
int right) {
assert(!m_prefixStack.empty()); // Constructor makes an empty entry
const std::string name{namep};
// An empty name means this is the root of a model created with
// name()=="". The tools get upset if we try to pass this as empty, so
// we put the signals under a new $rootio scope, but the signals
// further down will be peers, not children (as usual for name()!="").
const std::string prevPrefix = m_prefixStack.back().first;
if (name == "$rootio" && !prevPrefix.empty()) {
// Upper has name, we can suppress inserting $rootio, but still push so popPrefix works
m_prefixStack.emplace_back(prevPrefix, VerilatedTracePrefixType::ROOTIO_WRAPPER);
return;
}
if (name.empty()) {
m_prefixStack.emplace_back(prevPrefix, VerilatedTracePrefixType::ROOTIO_WRAPPER);
return;
}
bool isProperScope = true;
switch (type) {
case VerilatedTracePrefixType::ARRAY_PACKED:
case VerilatedTracePrefixType::ARRAY_UNPACKED: isProperScope = false; break;
default: break;
}
// This code assumes a signal at a given prefix level is declared before
// any pushPrefix are done at that same level.
const std::string newPrefix = prevPrefix + name;
m_prefixStack.emplace_back(newPrefix + (isProperScope ? " " : ""), type);
const uint32_t l = static_cast<uint32_t>(left);
const uint32_t r = static_cast<uint32_t>(right);
const uint64_t lr = static_cast<uint64_t>(l) << 32 | static_cast<uint64_t>(r);
switch (type) {
case VerilatedTracePrefixType::SCOPE_MODULE:
m_fst->setScope(fst::Hierarchy::ScopeType::VCD_MODULE, name, std::string{});
break;
case VerilatedTracePrefixType::SCOPE_INTERFACE:
m_fst->setScope(fst::Hierarchy::ScopeType::VCD_INTERFACE, name, std::string{});
break;
case VerilatedTracePrefixType::STRUCT_PACKED:
m_fst->setAttrBegin(fst::Hierarchy::AttrType::PACK,
fst::Hierarchy::AttrSubType::PACK_PACKED, "members", l);
m_fst->setScope(fst::Hierarchy::ScopeType::VCD_STRUCT, name, std::string{});
break;
case VerilatedTracePrefixType::STRUCT_UNPACKED:
m_fst->setAttrBegin(fst::Hierarchy::AttrType::PACK,
fst::Hierarchy::AttrSubType::PACK_UNPACKED, "members", l);
m_fst->setScope(fst::Hierarchy::ScopeType::VCD_STRUCT, name, std::string{});
break;
case VerilatedTracePrefixType::UNION_PACKED:
m_fst->setAttrBegin(fst::Hierarchy::AttrType::PACK,
fst::Hierarchy::AttrSubType::PACK_PACKED, "members", l);
m_fst->setScope(fst::Hierarchy::ScopeType::VCD_UNION, name, std::string{});
break;
case VerilatedTracePrefixType::ARRAY_PACKED:
m_fst->setAttrBegin(fst::Hierarchy::AttrType::ARRAY,
fst::Hierarchy::AttrSubType::ARRAY_PACKED, "bounds", lr);
m_fst->setScope(fst::Hierarchy::ScopeType::SV_ARRAY, name, std::string{});
break;
case VerilatedTracePrefixType::ARRAY_UNPACKED:
m_fst->setAttrBegin(fst::Hierarchy::AttrType::ARRAY,
fst::Hierarchy::AttrSubType::ARRAY_UNPACKED, "bounds", lr);
m_fst->setScope(fst::Hierarchy::ScopeType::SV_ARRAY, name, std::string{});
break;
default: break;
}
}
void VerilatedFst::popPrefix() {
assert(!m_prefixStack.empty());
if (m_prefixStack.back().second != VerilatedTracePrefixType::ROOTIO_WRAPPER) {
m_fst->upscope();
}
m_prefixStack.pop_back();
assert(!m_prefixStack.empty()); // Always one left, the constructor's initial one
}
void VerilatedFst::declare(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, bool array, int arraynum, bool bussed,
int msb, int lsb) {
const int bits = ((msb > lsb) ? (msb - lsb) : (lsb - msb)) + 1;
const std::string hierarchicalName = m_prefixStack.back().first + name;
const bool enabled = Super::declCode(code, hierarchicalName, bits);
if (!enabled) return;
assert(hierarchicalName.rfind(' ') != std::string::npos);
std::stringstream name_ss;
name_ss << lastWord(hierarchicalName);
if (array) name_ss << "[" << arraynum << "]";
if (bussed) name_ss << " [" << msb << ":" << lsb << "]";
const std::string name_str = name_ss.str();
if (dtypenum > 0) { m_fst->emitEnumTableRef(m_local2fstdtype.at(initUserp()).at(dtypenum)); }
fst::Hierarchy::VarDirection varDir = fst::Hierarchy::VarDirection::IMPLICIT;
switch (direction) {
case VerilatedTraceSigDirection::INOUT: varDir = fst::Hierarchy::VarDirection::INOUT; break;
case VerilatedTraceSigDirection::OUTPUT: varDir = fst::Hierarchy::VarDirection::OUTPUT; break;
case VerilatedTraceSigDirection::INPUT: varDir = fst::Hierarchy::VarDirection::INPUT; break;
case VerilatedTraceSigDirection::NONE: varDir = fst::Hierarchy::VarDirection::IMPLICIT; break;
}
fst::Hierarchy::VarType varType;
// Doubles have special decoding properties, so must indicate if a double
if (type == VerilatedTraceSigType::DOUBLE) {
if (kind == VerilatedTraceSigKind::PARAMETER) {
varType = fst::Hierarchy::VarType::VCD_REAL_PARAMETER;
} else {
varType = fst::Hierarchy::VarType::VCD_REAL;
}
}
// clang-format off
else if (kind == VerilatedTraceSigKind::PARAMETER) varType = fst::Hierarchy::VarType::VCD_PARAMETER;
else if (kind == VerilatedTraceSigKind::SUPPLY0) varType = fst::Hierarchy::VarType::VCD_SUPPLY0;
else if (kind == VerilatedTraceSigKind::SUPPLY1) varType = fst::Hierarchy::VarType::VCD_SUPPLY1;
else if (kind == VerilatedTraceSigKind::TRI) varType = fst::Hierarchy::VarType::VCD_TRI;
else if (kind == VerilatedTraceSigKind::TRI0) varType = fst::Hierarchy::VarType::VCD_TRI0;
else if (kind == VerilatedTraceSigKind::TRI1) varType = fst::Hierarchy::VarType::VCD_TRI1;
else if (kind == VerilatedTraceSigKind::TRIAND) varType = fst::Hierarchy::VarType::VCD_TRIAND;
else if (kind == VerilatedTraceSigKind::TRIOR) varType = fst::Hierarchy::VarType::VCD_TRIOR;
else if (kind == VerilatedTraceSigKind::TRIREG) varType = fst::Hierarchy::VarType::VCD_TRIREG; // LCOV_EXCL_BR_LINE
else if (kind == VerilatedTraceSigKind::WIRE) varType = fst::Hierarchy::VarType::VCD_WIRE;
//
else if (type == VerilatedTraceSigType::INTEGER) varType = fst::Hierarchy::VarType::VCD_INTEGER;
else if (type == VerilatedTraceSigType::BIT) varType = fst::Hierarchy::VarType::SV_BIT;
else if (type == VerilatedTraceSigType::LOGIC) varType = fst::Hierarchy::VarType::SV_LOGIC;
else if (type == VerilatedTraceSigType::INT) varType = fst::Hierarchy::VarType::SV_INT;
else if (type == VerilatedTraceSigType::SHORTINT) varType = fst::Hierarchy::VarType::SV_SHORTINT;
else if (type == VerilatedTraceSigType::LONGINT) varType = fst::Hierarchy::VarType::SV_LONGINT;
else if (type == VerilatedTraceSigType::BYTE) varType = fst::Hierarchy::VarType::SV_BYTE;
else if (type == VerilatedTraceSigType::EVENT) varType = fst::Hierarchy::VarType::VCD_EVENT;
else if (type == VerilatedTraceSigType::TIME) varType = fst::Hierarchy::VarType::VCD_TIME; // LCOV_EXCL_BR_LINE
else { assert(0); /* Unreachable */ }
// clang-format on
const auto it = vlstd::as_const(m_code2symbol).find(code);
if (it == m_code2symbol.end()) { // New
m_code2symbol[code] = m_fst->createVar(varType, varDir, bits, name_str.c_str(), 0);
} else { // Alias
m_fst->createVar(varType, varDir, bits, name_str.c_str(), it->second);
}
}
// versions to call when the sig is not array member
void VerilatedFst::declEvent(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type) {
declare(code, name, dtypenum, direction, kind, type, false, -1, false, 0, 0);
}
void VerilatedFst::declBit(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type) {
declare(code, name, dtypenum, direction, kind, type, false, -1, false, 0, 0);
}
void VerilatedFst::declBus(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, int msb, int lsb) {
declare(code, name, dtypenum, direction, kind, type, false, -1, true, msb, lsb);
}
void VerilatedFst::declQuad(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, int msb, int lsb) {
declare(code, name, dtypenum, direction, kind, type, false, -1, true, msb, lsb);
}
void VerilatedFst::declWide(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, int msb, int lsb) {
declare(code, name, dtypenum, direction, kind, type, false, -1, true, msb, lsb);
}
void VerilatedFst::declDouble(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type) {
declare(code, name, dtypenum, direction, kind, type, false, -1, false, 63, 0);
}
// versions to call when the sig is array member
void VerilatedFst::declEventArray(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, int arraynum) {
declare(code, name, dtypenum, direction, kind, type, true, arraynum, false, 0, 0);
}
void VerilatedFst::declBitArray(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, int arraynum) {
declare(code, name, dtypenum, direction, kind, type, true, arraynum, false, 0, 0);
}
void VerilatedFst::declBusArray(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, int arraynum, int msb, int lsb) {
declare(code, name, dtypenum, direction, kind, type, true, arraynum, true, msb, lsb);
}
void VerilatedFst::declQuadArray(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, int arraynum, int msb, int lsb) {
declare(code, name, dtypenum, direction, kind, type, true, arraynum, true, msb, lsb);
}
void VerilatedFst::declWideArray(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, int arraynum, int msb, int lsb) {
declare(code, name, dtypenum, direction, kind, type, true, arraynum, true, msb, lsb);
}
void VerilatedFst::declDoubleArray(uint32_t code, const char* name, int dtypenum,
VerilatedTraceSigDirection direction,
VerilatedTraceSigKind kind, VerilatedTraceSigType type,
int arraynum) {
declare(code, name, dtypenum, direction, kind, type, true, arraynum, false, 63, 0);
}
//=============================================================================
// Get/commit trace buffer
VerilatedFst::Buffer* VerilatedFst::getTraceBuffer(uint32_t /*fidx*/) { return new Buffer{*this}; }
void VerilatedFst::commitTraceBuffer(VerilatedFst::Buffer* bufp) { delete bufp; }
//=============================================================================
// VerilatedFstBuffer implementation
//=============================================================================
// Trace rendering primitives
// Note: emit* are only ever called from one place (full* in
// verilated_trace_imp.h, which is included in this file at the top),
// so always inline them.
VL_ATTR_ALWINLINE
void VerilatedFstBuffer::emitEvent(uint32_t code) {
VL_DEBUG_IFDEF(assert(m_symbolp[code]);); // LCOV_EXCL_BR_LINE
m_owner.emitTimeChangeMaybe();
m_fst->emitValueChange(m_symbolp[code], 1);
}
VL_ATTR_ALWINLINE
void VerilatedFstBuffer::emitBit(uint32_t code, CData newval) {
VL_DEBUG_IFDEF(assert(m_symbolp[code]);); // LCOV_EXCL_BR_LINE
m_owner.emitTimeChangeMaybe();
m_fst->emitValueChange(m_symbolp[code], uint64_t(newval));
}
VL_ATTR_ALWINLINE
void VerilatedFstBuffer::emitCData(uint32_t code, CData newval, int) {
VL_DEBUG_IFDEF(assert(m_symbolp[code]);); // LCOV_EXCL_BR_LINE
m_owner.emitTimeChangeMaybe();
m_fst->emitValueChange(m_symbolp[code], newval);
}
VL_ATTR_ALWINLINE
void VerilatedFstBuffer::emitSData(uint32_t code, SData newval, int) {
VL_DEBUG_IFDEF(assert(m_symbolp[code]);); // LCOV_EXCL_BR_LINE
m_owner.emitTimeChangeMaybe();
m_fst->emitValueChange(m_symbolp[code], newval);
}
VL_ATTR_ALWINLINE
void VerilatedFstBuffer::emitIData(uint32_t code, IData newval, int) {
VL_DEBUG_IFDEF(assert(m_symbolp[code]);); // LCOV_EXCL_BR_LINE
m_owner.emitTimeChangeMaybe();
m_fst->emitValueChange(m_symbolp[code], newval);
}
VL_ATTR_ALWINLINE
void VerilatedFstBuffer::emitQData(uint32_t code, QData newval, int) {
VL_DEBUG_IFDEF(assert(m_symbolp[code]);); // LCOV_EXCL_BR_LINE
m_owner.emitTimeChangeMaybe();
m_fst->emitValueChange(m_symbolp[code], newval);
}
VL_ATTR_ALWINLINE
void VerilatedFstBuffer::emitWData(uint32_t code, WDataInP newval, int) {
VL_DEBUG_IFDEF(assert(m_symbolp[code]);); // LCOV_EXCL_BR_LINE
m_owner.emitTimeChangeMaybe();
m_fst->emitValueChange(m_symbolp[code], newval.datap());
}
VL_ATTR_ALWINLINE
void VerilatedFstBuffer::emitDouble(uint32_t code, double newval) {
VL_DEBUG_IFDEF(assert(m_symbolp[code]);); // LCOV_EXCL_BR_LINE
m_owner.emitTimeChangeMaybe();
uint64_t newval_u64;
std::memcpy(&newval_u64, &newval, sizeof(newval_u64));
m_fst->emitValueChange(m_symbolp[code], newval_u64);
}