Skip to content

Commit

Permalink
Unify code generation for trace declarations in both trace formats
Browse files Browse the repository at this point in the history
This patch adds some abstract enums to pass to the trace decl* APIs, so
the VCD/FST specific code can be kept in verilated_{vcd,fst}_*.cc, and
removed from V3Emit*. It also reworks the generation of the trace init
functions (those that call 'decl*' for the signals) such that the scope
hierarchy is traversed precisely once during initialization, which
simplifies the FST writer. This later change also has the side effect of
fixing tracing of nested interfaces when traced via an interface
reference - see the change in the expected t_interface_ref_trace - which
previously were missed.
  • Loading branch information
gezalore committed Oct 23, 2023
1 parent d1b6224 commit 112b3e5
Show file tree
Hide file tree
Showing 22 changed files with 12,261 additions and 12,005 deletions.
200 changes: 110 additions & 90 deletions include/verilated_fst_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

// clang-format off

#define __STDC_LIMIT_MACROS // UINT64_MAX
#include "verilated.h"
#include "verilated_fst_c.h"

Expand All @@ -40,6 +39,7 @@
#include <algorithm>
#include <iterator>
#include <sstream>
#include <type_traits>

#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
# include <io.h>
Expand All @@ -50,33 +50,10 @@
// clang-format on

//=============================================================================
// Check that vltscope_t matches fstScopeType

static_assert(static_cast<int>(FST_ST_VCD_MODULE) == static_cast<int>(VLT_TRACE_SCOPE_MODULE),
"VLT_TRACE_SCOPE_MODULE mismatches");
static_assert(static_cast<int>(FST_ST_VCD_TASK) == static_cast<int>(VLT_TRACE_SCOPE_TASK),
"VLT_TRACE_SCOPE_TASK mismatches");
static_assert(static_cast<int>(FST_ST_VCD_FUNCTION) == static_cast<int>(VLT_TRACE_SCOPE_FUNCTION),
"VLT_TRACE_SCOPE_FUNCTION mismatches");
static_assert(static_cast<int>(FST_ST_VCD_BEGIN) == static_cast<int>(VLT_TRACE_SCOPE_BEGIN),
"VLT_TRACE_SCOPE_BEGIN mismatches");
static_assert(static_cast<int>(FST_ST_VCD_FORK) == static_cast<int>(VLT_TRACE_SCOPE_FORK),
"VLT_TRACE_SCOPE_FORK mismatches");
static_assert(static_cast<int>(FST_ST_VCD_GENERATE) == static_cast<int>(VLT_TRACE_SCOPE_GENERATE),
"VLT_TRACE_SCOPE_GENERATE mismatches");
static_assert(static_cast<int>(FST_ST_VCD_STRUCT) == static_cast<int>(VLT_TRACE_SCOPE_STRUCT),
"VLT_TRACE_SCOPE_STRUCT mismatches");
static_assert(static_cast<int>(FST_ST_VCD_UNION) == static_cast<int>(VLT_TRACE_SCOPE_UNION),
"VLT_TRACE_SCOPE_UNION mismatches");
static_assert(static_cast<int>(FST_ST_VCD_CLASS) == static_cast<int>(VLT_TRACE_SCOPE_CLASS),
"VLT_TRACE_SCOPE_CLASS mismatches");
static_assert(static_cast<int>(FST_ST_VCD_INTERFACE)
== static_cast<int>(VLT_TRACE_SCOPE_INTERFACE),
"VLT_TRACE_SCOPE_INTERFACE mismatches");
static_assert(static_cast<int>(FST_ST_VCD_PACKAGE) == static_cast<int>(VLT_TRACE_SCOPE_PACKAGE),
"VLT_TRACE_SCOPE_PACKAGE mismatches");
static_assert(static_cast<int>(FST_ST_VCD_PROGRAM) == static_cast<int>(VLT_TRACE_SCOPE_PROGRAM),
"VLT_TRACE_SCOPE_PROGRAM mismatches");
// Check that forward declared types matches the FST API types

static_assert(std::is_same<vlFstHandle, fstHandle>::value, "vlFstHandle mismatch");
static_assert(std::is_same<vlFstEnumHandle, fstEnumHandle>::value, "vlFstHandle mismatch");

//=============================================================================
// Specialization of the generics for this trace format
Expand Down Expand Up @@ -155,99 +132,142 @@ void VerilatedFst::declDTypeEnum(int dtypenum, const char* name, uint32_t elemen
m_local2fstdtype[dtypenum] = enumNum;
}

void VerilatedFst::declare(uint32_t code, const char* name, int dtypenum, fstVarDir vardir,
fstVarType vartype, bool array, int arraynum, bool bussed, int msb,
int lsb) {
const int bits = ((msb > lsb) ? (msb - lsb) : (lsb - msb)) + 1;
// TODO: should return std::optional<fstScopeType>, but I can't have C++17
static std::pair<bool, fstScopeType> toFstScopeType(VerilatedTracePrefixType type) {
switch (type) {
case VerilatedTracePrefixType::SCOPE_MODULE: return {true, FST_ST_VCD_MODULE};
case VerilatedTracePrefixType::SCOPE_INTERFACE: return {true, FST_ST_VCD_INTERFACE};
case VerilatedTracePrefixType::STRUCT_PACKED:
case VerilatedTracePrefixType::STRUCT_UNPACKED: return {true, FST_ST_VCD_STRUCT};
case VerilatedTracePrefixType::UNION_PACKED: return {true, FST_ST_VCD_UNION};
default: return {false, /* unused so whatever, just need a value */ FST_ST_VCD_SCOPE};
}
}

const bool enabled = Super::declCode(code, name, bits, false);
if (!enabled) return;
// Return last ' ' separated word. Assumes string does not end in whitespace.
std::string lastWord(const std::string& str) {
const size_t idx = str.rfind(' ');
if (idx == std::string::npos) return str;
return str.substr(idx + 1);
}

std::string nameasstr = namePrefix() + name;
std::istringstream nameiss{nameasstr};
std::istream_iterator<std::string> beg(nameiss);
std::istream_iterator<std::string> end;
std::list<std::string> tokens(beg, end); // Split name
std::string symbol_name{tokens.back()};
tokens.pop_back(); // Remove symbol name from hierarchy
std::string tmpModName;

// Find point where current and new scope diverge
auto cur_it = m_curScope.begin();
auto new_it = tokens.begin();
while (cur_it != m_curScope.end() && new_it != tokens.end()) {
if (*cur_it != *new_it) break;
++cur_it;
++new_it;
void VerilatedFst::pushPrefix(const std::string& name, VerilatedTracePrefixType type) {
const std::string newPrefix = m_prefixStack.back().first + name;
const auto pair = toFstScopeType(type);
const bool properScope = pair.first;
const fstScopeType scopeType = pair.second;
m_prefixStack.emplace_back(newPrefix + (properScope ? " " : ""), type);
if (properScope) {
const std::string scopeName = lastWord(newPrefix);
fstWriterSetScope(m_fst, scopeType, scopeName.c_str(), nullptr);
}
}

// Go back to the common point
while (cur_it != m_curScope.end()) {
fstWriterSetUpscope(m_fst);
cur_it = m_curScope.erase(cur_it);
}
void VerilatedFst::popPrefix() {
const bool properScope = toFstScopeType(m_prefixStack.back().second).first;
if (properScope) fstWriterSetUpscope(m_fst);
m_prefixStack.pop_back();
assert(!m_prefixStack.empty());
}

// Follow the hierarchy of the new variable from the common scope point
while (new_it != tokens.end()) {
if ((new_it->back() & 0x80)) {
tmpModName = *new_it;
tmpModName.pop_back();
// If the scope ends with a non-ASCII character, it will be 0x80 + fstScopeType
fstWriterSetScope(m_fst, static_cast<fstScopeType>(new_it->back() & 0x7f),
tmpModName.c_str(), nullptr);
} else {
fstWriterSetScope(m_fst, FST_ST_VCD_SCOPE, new_it->c_str(), nullptr);
}
m_curScope.push_back(*new_it);
new_it = tokens.erase(new_it);
}
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, false);
if (!enabled) return;

assert(hierarchicalName.rfind(' ') != std::string::npos);
std::stringstream name_ss;
name_ss << symbol_name;
name_ss << lastWord(hierarchicalName);
if (array) name_ss << "[" << arraynum << "]";
if (bussed) name_ss << " [" << msb << ":" << lsb << "]";
std::string name_str = name_ss.str();
const std::string name_str = name_ss.str();

if (dtypenum > 0) {
const fstEnumHandle enumNum = m_local2fstdtype[dtypenum];
fstWriterEmitEnumTableRef(m_fst, enumNum);
if (dtypenum > 0) fstWriterEmitEnumTableRef(m_fst, m_local2fstdtype[dtypenum]);

fstVarDir varDir;
switch (direction) {
case VerilatedTraceSigDirection::Inout: varDir = FST_VD_INOUT; break;
case VerilatedTraceSigDirection::Output: varDir = FST_VD_OUTPUT; break;
case VerilatedTraceSigDirection::Input: varDir = FST_VD_INPUT; break;
case VerilatedTraceSigDirection::None: varDir = FST_VD_IMPLICIT; break;
}

fstVarType varType;
// Doubles have special decoding properties, so must indicate if a double
if (type == VerilatedTraceSigType::Double) {
if (kind == VerilatedTraceSigKind::Parameter) {
varType = FST_VT_VCD_REAL_PARAMETER;
} else {
varType = FST_VT_VCD_REAL;
}
}
// clang-format off
else if (kind == VerilatedTraceSigKind::Parameter) varType = FST_VT_VCD_PARAMETER;
else if (kind == VerilatedTraceSigKind::Supply0) varType = FST_VT_VCD_SUPPLY0;
else if (kind == VerilatedTraceSigKind::Supply1) varType = FST_VT_VCD_SUPPLY1;
else if (kind == VerilatedTraceSigKind::Tri0) varType = FST_VT_VCD_TRI0;
else if (kind == VerilatedTraceSigKind::Tri1) varType = FST_VT_VCD_TRI1;
else if (kind == VerilatedTraceSigKind::Triwire) varType = FST_VT_VCD_TRI;
else if (kind == VerilatedTraceSigKind::Wire) varType = FST_VT_VCD_WIRE;
//
else if (type == VerilatedTraceSigType::Integer) varType = FST_VT_VCD_INTEGER;
else if (type == VerilatedTraceSigType::Bit) varType = FST_VT_SV_BIT;
else if (type == VerilatedTraceSigType::Logic) varType = FST_VT_SV_LOGIC;
else if (type == VerilatedTraceSigType::Int) varType = FST_VT_SV_INT;
else if (type == VerilatedTraceSigType::Shortint) varType = FST_VT_SV_SHORTINT;
else if (type == VerilatedTraceSigType::Longint) varType = FST_VT_SV_LONGINT;
else if (type == VerilatedTraceSigType::Byte) varType = FST_VT_SV_BYTE;
else if (type == VerilatedTraceSigType::Event) varType = FST_VT_VCD_EVENT;
else varType = FST_VT_SV_BIT;
// clang-format on

const auto it = vlstd::as_const(m_code2symbol).find(code);
if (it == m_code2symbol.end()) { // New
m_code2symbol[code]
= fstWriterCreateVar(m_fst, vartype, vardir, bits, name_str.c_str(), 0);
= fstWriterCreateVar(m_fst, varType, varDir, bits, name_str.c_str(), 0);
} else { // Alias
fstWriterCreateVar(m_fst, vartype, vardir, bits, name_str.c_str(), it->second);
fstWriterCreateVar(m_fst, varType, varDir, bits, name_str.c_str(), it->second);
}
}

void VerilatedFst::declEvent(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
fstVarDir vardir, fstVarType vartype, bool array, int arraynum) {
declare(code, name, dtypenum, vardir, vartype, array, arraynum, false, 0, 0);
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, bool array, int arraynum) {
declare(code, name, dtypenum, direction, kind, type, array, arraynum, false, 0, 0);
}
void VerilatedFst::declBit(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
fstVarDir vardir, fstVarType vartype, bool array, int arraynum) {
declare(code, name, dtypenum, vardir, vartype, array, arraynum, false, 0, 0);
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, bool array, int arraynum) {
declare(code, name, dtypenum, direction, kind, type, array, arraynum, false, 0, 0);
}
void VerilatedFst::declBus(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
fstVarDir vardir, fstVarType vartype, bool array, int arraynum, int msb,
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, bool array, int arraynum, int msb,
int lsb) {
declare(code, name, dtypenum, vardir, vartype, array, arraynum, true, msb, lsb);
declare(code, name, dtypenum, direction, kind, type, array, arraynum, true, msb, lsb);
}
void VerilatedFst::declQuad(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
fstVarDir vardir, fstVarType vartype, bool array, int arraynum,
int msb, int lsb) {
declare(code, name, dtypenum, vardir, vartype, array, arraynum, true, msb, lsb);
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, bool array, int arraynum, int msb,
int lsb) {
declare(code, name, dtypenum, direction, kind, type, array, arraynum, true, msb, lsb);
}
void VerilatedFst::declArray(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
fstVarDir vardir, fstVarType vartype, bool array, int arraynum,
int msb, int lsb) {
declare(code, name, dtypenum, vardir, vartype, array, arraynum, true, msb, lsb);
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, bool array, int arraynum, int msb,
int lsb) {
declare(code, name, dtypenum, direction, kind, type, array, arraynum, true, msb, lsb);
}
void VerilatedFst::declDouble(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
fstVarDir vardir, fstVarType vartype, bool array, int arraynum) {
declare(code, name, dtypenum, vardir, vartype, array, arraynum, false, 63, 0);
VerilatedTraceSigDirection direction, VerilatedTraceSigKind kind,
VerilatedTraceSigType type, bool array, int arraynum) {
declare(code, name, dtypenum, direction, kind, type, array, arraynum, false, 63, 0);
}

//=============================================================================
Expand Down
55 changes: 35 additions & 20 deletions include/verilated_fst_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
#include "verilated.h"
#include "verilated_trace.h"

#include "gtkwave/fstapi.h"

#include <list>
#include <map>
#include <string>
#include <vector>

typedef uint32_t vlFstHandle;
typedef uint32_t vlFstEnumHandle;

class VerilatedFstBuffer;

//=============================================================================
Expand All @@ -49,18 +50,23 @@ class VerilatedFst final : public VerilatedTrace<VerilatedFst, VerilatedFstBuffe
// FST specific internals

void* m_fst = nullptr;
std::map<uint32_t, fstHandle> m_code2symbol;
std::map<int, fstEnumHandle> m_local2fstdtype;
std::map<uint32_t, vlFstHandle> m_code2symbol;
std::map<int, vlFstEnumHandle> m_local2fstdtype;
std::list<std::string> m_curScope;
fstHandle* m_symbolp = nullptr; // same as m_code2symbol, but as an array
vlFstHandle* m_symbolp = nullptr; // same as m_code2symbol, but as an array
char* m_strbufp = nullptr; // String buffer long enough to hold maxBits() chars

bool m_useFstWriterThread = false; // Whether to use the separate FST writer thread

// Prefixes to add to signal names/scope types
std::vector<std::pair<std::string, VerilatedTracePrefixType>> m_prefixStack{
{"", VerilatedTracePrefixType::SCOPE_MODULE}};

// CONSTRUCTORS
VL_UNCOPYABLE(VerilatedFst);
void declare(uint32_t code, const char* name, int dtypenum, fstVarDir vardir,
fstVarType vartype, bool array, int arraynum, bool bussed, int msb, int lsb);
void declare(uint32_t code, const char* name, int dtypenum, VerilatedTraceSigDirection,
VerilatedTraceSigKind, VerilatedTraceSigType, bool array, int arraynum,
bool bussed, int msb, int lsb);

protected:
//=========================================================================
Expand Down Expand Up @@ -101,18 +107,27 @@ class VerilatedFst final : public VerilatedTrace<VerilatedFst, VerilatedFstBuffe
//=========================================================================
// Internal interface to Verilator generated code

void declEvent(uint32_t code, uint32_t fidx, const char* name, int dtypenum, fstVarDir vardir,
fstVarType vartype, bool array, int arraynum);
void declBit(uint32_t code, uint32_t fidx, const char* name, int dtypenum, fstVarDir vardir,
fstVarType vartype, bool array, int arraynum);
void declBus(uint32_t code, uint32_t fidx, const char* name, int dtypenum, fstVarDir vardir,
fstVarType vartype, bool array, int arraynum, int msb, int lsb);
void declQuad(uint32_t code, uint32_t fidx, const char* name, int dtypenum, fstVarDir vardir,
fstVarType vartype, bool array, int arraynum, int msb, int lsb);
void declArray(uint32_t code, uint32_t fidx, const char* name, int dtypenum, fstVarDir vardir,
fstVarType vartype, bool array, int arraynum, int msb, int lsb);
void declDouble(uint32_t code, uint32_t fidx, const char* name, int dtypenum, fstVarDir vardir,
fstVarType vartype, bool array, int arraynum);
void pushPrefix(const std::string&, VerilatedTracePrefixType);
void popPrefix();

void declEvent(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
VerilatedTraceSigDirection, VerilatedTraceSigKind, VerilatedTraceSigType,
bool array, int arraynum);
void declBit(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
VerilatedTraceSigDirection, VerilatedTraceSigKind, VerilatedTraceSigType,
bool array, int arraynum);
void declBus(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
VerilatedTraceSigDirection, VerilatedTraceSigKind, VerilatedTraceSigType,
bool array, int arraynum, int msb, int lsb);
void declQuad(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
VerilatedTraceSigDirection, VerilatedTraceSigKind, VerilatedTraceSigType,
bool array, int arraynum, int msb, int lsb);
void declArray(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
VerilatedTraceSigDirection, VerilatedTraceSigKind, VerilatedTraceSigType,
bool array, int arraynum, int msb, int lsb);
void declDouble(uint32_t code, uint32_t fidx, const char* name, int dtypenum,
VerilatedTraceSigDirection, VerilatedTraceSigKind, VerilatedTraceSigType,
bool array, int arraynum);

void declDTypeEnum(int dtypenum, const char* name, uint32_t elements, unsigned int minValbits,
const char** itemNamesp, const char** itemValuesp);
Expand Down Expand Up @@ -149,7 +164,7 @@ class VerilatedFstBuffer VL_NOT_FINAL {
// The FST file handle
void* const m_fst = m_owner.m_fst;
// code to fstHande map, as an array
const fstHandle* const m_symbolp = m_owner.m_symbolp;
const vlFstHandle* const m_symbolp = m_owner.m_symbolp;
// String buffer long enough to hold maxBits() chars
char* const m_strbufp = m_owner.m_strbufp;

Expand Down
Loading

0 comments on commit 112b3e5

Please sign in to comment.