diff --git a/include/verilated.mk.in b/include/verilated.mk.in index 10bf9f100..e89880a2d 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -90,6 +90,7 @@ VK_CPPFLAGS_ALWAYS += \ -DVM_TRACE=$(VM_TRACE) \ -DVM_TRACE_FST=$(VM_TRACE_FST) \ -DVM_TRACE_VCD=$(VM_TRACE_VCD) \ + -DVM_TRACE_SAIF=$(VM_TRACE_SAIF) \ $(CFG_CXXFLAGS_NO_UNUSED) \ ifeq ($(CFG_WITH_CCWARN),yes) # Local... Else don't burden users diff --git a/include/verilated_saif_c.cpp b/include/verilated_saif_c.cpp new file mode 100644 index 000000000..dab198775 --- /dev/null +++ b/include/verilated_saif_c.cpp @@ -0,0 +1,547 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// Copyright 2001-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 C++ tracing in SAIF format implementation code +/// +/// This file must be compiled and linked against all Verilated objects +/// that use --trace. +/// +/// Use "verilator --trace" to add this to the Makefile for the linker. +/// +//============================================================================= + +// clang-format off + +#include "verilatedos.h" +#include "verilated.h" +#include "verilated_saif_c.h" + +#include +#include +#include +#include + +#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) +# include +#else +# include +#endif + +#ifndef O_LARGEFILE // WIN32 headers omit this +# define O_LARGEFILE 0 +#endif +#ifndef O_NONBLOCK // WIN32 headers omit this +# define O_NONBLOCK 0 +#endif +#ifndef O_CLOEXEC // WIN32 headers omit this +# define O_CLOEXEC 0 +#endif + +// clang-format on + +// This size comes form SAIF allowing use of printable ASCII characters between +// '!' and '~' inclusive, which are a total of 94 different values. Encoding a +// 32 bit code hence needs a maximum of std::ceil(log94(2**32-1)) == 5 bytes. +constexpr unsigned VL_TRACE_MAX_SAIF_CODE_SIZE = 5; // Maximum length of a SAIF string code + +// We use 8 bytes per code in a suffix buffer array. +// 1 byte optional separator + VL_TRACE_MAX_SAIF_CODE_SIZE bytes for code +// + 1 byte '\n' + 1 byte suffix size. This luckily comes out to a power of 2, +// meaning the array can be aligned such that entries never straddle multiple +// cache-lines. +constexpr unsigned VL_TRACE_SUFFIX_ENTRY_SIZE = 8; // Size of a suffix entry + +//============================================================================= +// Specialization of the generics for this trace format + +#define VL_SUB_T VerilatedSaif +#define VL_BUF_T VerilatedSaifBuffer +#include "verilated_trace_imp.h" +#undef VL_SUB_T +#undef VL_BUF_T + +//============================================================================= +//============================================================================= +//============================================================================= +// VerilatedSaifFile + +bool VerilatedSaifFile::open(const std::string& name) VL_MT_UNSAFE { + m_fd = ::open(name.c_str(), + O_CREAT | O_WRONLY | O_TRUNC | O_LARGEFILE | O_NONBLOCK | O_CLOEXEC, 0666); + return m_fd >= 0; +} + +void VerilatedSaifFile::close() VL_MT_UNSAFE { ::close(m_fd); } + +ssize_t VerilatedSaifFile::write(const char* bufp, ssize_t len) VL_MT_UNSAFE { + return ::write(m_fd, bufp, len); +} + +//============================================================================= +//============================================================================= +//============================================================================= +// Opening/Closing + +VerilatedSaif::VerilatedSaif(VerilatedSaifFile* filep) { + // Not in header to avoid link issue if header is included without this .cpp file + m_fileNewed = (filep == nullptr); + m_filep = m_fileNewed ? new VerilatedSaifFile : filep; + m_wrChunkSize = 8 * 1024; + m_wrBufp = new char[m_wrChunkSize * 8]; + m_wrFlushp = m_wrBufp + m_wrChunkSize * 6; + m_writep = m_wrBufp; +} + +void VerilatedSaif::open(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex) { + const VerilatedLockGuard lock{m_mutex}; + if (isOpen()) return; + + // Set member variables + m_filename = filename; // "" is ok, as someone may overload open + + openNextImp(m_rolloverSize != 0); + if (!isOpen()) return; + + printStr("(SAIFILE\n"); + printStr("(SAIFVERSION \"2.0\")\n"); + printStr("(DIRECTION \"backward\")\n"); + printStr("(DESIGN \"foo\")\n"); + printStr("(PROGRAM_NAME \"Verilator\")\n"); + printStr("(VERSION \"5.032\")\n"); + printStr("(DIVIDER .)\n"); + printStr("(TIMESCALE "); + printStr(timeResStr().c_str()); + printStr(")\n"); + + Super::traceInit(); + + // When using rollover, the first chunk contains the header only. + if (m_rolloverSize) openNextImp(true); +} + +void VerilatedSaif::openNext(bool incFilename) VL_MT_SAFE_EXCLUDES(m_mutex) { + // Open next filename in concat sequence, mangle filename if + // incFilename is true. + const VerilatedLockGuard lock{m_mutex}; + openNextImp(incFilename); +} + +void VerilatedSaif::openNextImp(bool incFilename) { + closePrev(); // Close existing + if (incFilename) { + // Find _0000.{ext} in filename + std::string name = m_filename; + const size_t pos = name.rfind('.'); + if (pos > 8 && 0 == std::strncmp("_cat", name.c_str() + pos - 8, 4) + && std::isdigit(name.c_str()[pos - 4]) && std::isdigit(name.c_str()[pos - 3]) + && std::isdigit(name.c_str()[pos - 2]) && std::isdigit(name.c_str()[pos - 1])) { + // Increment code. + if ((++(name[pos - 1])) > '9') { + name[pos - 1] = '0'; + if ((++(name[pos - 2])) > '9') { + name[pos - 2] = '0'; + if ((++(name[pos - 3])) > '9') { + name[pos - 3] = '0'; + if ((++(name[pos - 4])) > '9') { // + name[pos - 4] = '0'; + } + } + } + } + } else { + // Append _cat0000 + name.insert(pos, "_cat0000"); + } + m_filename = name; + } + if (VL_UNCOVERABLE(m_filename[0] == '|')) { + assert(0); // LCOV_EXCL_LINE // Not supported yet. + } else { + // cppcheck-suppress duplicateExpression + if (!m_filep->open(m_filename)) { + // User code can check isOpen() + m_isOpen = false; + return; + } + } + m_isOpen = true; + constDump(true); // First dump must containt the const signals + fullDump(true); // First dump must be full + m_wroteBytes = 0; +} + +bool VerilatedSaif::preChangeDump() { + if (VL_UNLIKELY(m_rolloverSize && m_wroteBytes > m_rolloverSize)) openNextImp(true); + return isOpen(); +} + +void VerilatedSaif::emitTimeChange(uint64_t timeui) { + m_time = timeui; +} + +VerilatedSaif::~VerilatedSaif() { + close(); + if (m_wrBufp) VL_DO_CLEAR(delete[] m_wrBufp, m_wrBufp = nullptr); + if (m_filep && m_fileNewed) VL_DO_CLEAR(delete m_filep, m_filep = nullptr); + if (parallel()) { + assert(m_numBuffers == m_freeBuffers.size()); + for (auto& pair : m_freeBuffers) VL_DO_CLEAR(delete[] pair.first, pair.first = nullptr); + } +} + +void VerilatedSaif::closePrev() { + // This function is on the flush() call path + if (!isOpen()) return; + + Super::flushBase(); + bufferFlush(); + m_isOpen = false; + m_filep->close(); +} + +void VerilatedSaif::closeErr() { + // This function is on the flush() call path + // Close due to an error. We might abort before even getting here, + // depending on the definition of vl_fatal. + if (!isOpen()) return; + + // No buffer flush, just fclose + m_isOpen = false; + m_filep->close(); // May get error, just ignore it +} + +void VerilatedSaif::close() VL_MT_SAFE_EXCLUDES(m_mutex) { + assert(m_time > 0); + printStr("(DURATION "); + printStr(std::to_string(m_time).c_str()); + printStr(")\n"); + printStr("(INSTANCE foo (NET\n"); + for (auto& activity : m_activity) { + for (size_t i = 0; i < activity.width; i++) { + auto& bit = activity.bits[i]; + if (bit.lastVal && activity.lastTime < m_time) { + bit.highTime += m_time - activity.lastTime; + } + if (!bit.transitions) { + // FIXME for some reason, signals are duplicated. + // The duplicates have no transitions, so we skip them. + continue; + } + assert(m_time >= bit.highTime); + printStr("("); + printStr(activity.name); + if (activity.width > 1) { + printStr("["); + printStr(std::to_string(activity.lsb + i).c_str()); + printStr("]"); + } + printStr(" (T0 "); + printStr(std::to_string(m_time - bit.highTime).c_str()); + printStr(") (T1 "); + printStr(std::to_string(bit.highTime).c_str()); + printStr(") (TX 0) (TC "); + printStr(std::to_string(bit.transitions).c_str()); + printStr("))\n"); + } + activity.lastTime = m_time; + } + printStr("))"); // INSTANCE/NET + printStr(")\n"); // SAIFILE + // This function is on the flush() call path + const VerilatedLockGuard lock{m_mutex}; + if (!isOpen()) return; + closePrev(); + // closePrev() called Super::flush(), so we just + // need to shut down the tracing thread here. + Super::closeBase(); +} + +void VerilatedSaif::flush() VL_MT_SAFE_EXCLUDES(m_mutex) { + const VerilatedLockGuard lock{m_mutex}; + Super::flushBase(); + bufferFlush(); +} + +void VerilatedSaif::printStr(const char* str) { + m_filep->write(str, strlen(str)); +} + +void VerilatedSaif::bufferResize(size_t minsize) { + // minsize is size of largest write. We buffer at least 8 times as much data, + // writing when we are 3/4 full (with thus 2*minsize remaining free) + if (VL_UNLIKELY(minsize > m_wrChunkSize)) { + const char* oldbufp = m_wrBufp; + m_wrChunkSize = roundUpToMultipleOf<1024>(minsize * 2); + m_wrBufp = new char[m_wrChunkSize * 8]; + std::memcpy(m_wrBufp, oldbufp, m_writep - oldbufp); + m_writep = m_wrBufp + (m_writep - oldbufp); + m_wrFlushp = m_wrBufp + m_wrChunkSize * 6; + VL_DO_CLEAR(delete[] oldbufp, oldbufp = nullptr); + } +} + +void VerilatedSaif::bufferFlush() VL_MT_UNSAFE_ONE { + // This function can be called from the trace offload thread + // This function is on the flush() call path + // We add output data to m_writep. + // When it gets nearly full we dump it using this routine which calls write() + // This is much faster than using buffered I/O + if (VL_UNLIKELY(!m_isOpen)) return; + const char* wp = m_wrBufp; + while (true) { + const ssize_t remaining = (m_writep - wp); + if (remaining == 0) break; + errno = 0; + const ssize_t got = m_filep->write(wp, remaining); + if (got > 0) { + wp += got; + m_wroteBytes += got; + } else if (VL_UNCOVERABLE(got < 0)) { + if (VL_UNCOVERABLE(errno != EAGAIN && errno != EINTR)) { + // LCOV_EXCL_START + // write failed, presume error (perhaps out of disk space) + const std::string msg = "VerilatedSaif::bufferFlush: "s + std::strerror(errno); + VL_FATAL_MT("", 0, "", msg.c_str()); + closeErr(); + break; + // LCOV_EXCL_STOP + } + } + } + + // Reset buffer + m_writep = m_wrBufp; +} + +//============================================================================= +// Definitions + +void VerilatedSaif::printIndent(int level_change) { + if (level_change < 0) m_indent += level_change; + for (int i = 0; i < m_indent; ++i) printStr(" "); + if (level_change > 0) m_indent += level_change; +} + +void VerilatedSaif::pushPrefix(const std::string& name, VerilatedTracePrefixType type) { + assert(!m_prefixStack.empty()); // Constructor makes an empty entry + std::string pname = name; + // 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 scope, but the signals further down will be peers, not children (as usual + // for name()!="") + // Terminate earlier $root? + if (m_prefixStack.back().second == VerilatedTracePrefixType::ROOTIO_MODULE) popPrefix(); + if (pname.empty()) { // Start new temporary root + pname = "$rootio"; // SAIF names are not backslash escaped + m_prefixStack.emplace_back("", VerilatedTracePrefixType::ROOTIO_WRAPPER); + type = VerilatedTracePrefixType::ROOTIO_MODULE; + } + std::string newPrefix = m_prefixStack.back().first + pname; + switch (type) { + case VerilatedTracePrefixType::ROOTIO_MODULE: + case VerilatedTracePrefixType::SCOPE_MODULE: + case VerilatedTracePrefixType::SCOPE_INTERFACE: + case VerilatedTracePrefixType::STRUCT_PACKED: + case VerilatedTracePrefixType::STRUCT_UNPACKED: + case VerilatedTracePrefixType::UNION_PACKED: { + newPrefix += ' '; + break; + } + default: break; + } + m_prefixStack.emplace_back(newPrefix, type); +} + +void VerilatedSaif::popPrefix() { + assert(!m_prefixStack.empty()); + switch (m_prefixStack.back().second) { + case VerilatedTracePrefixType::ROOTIO_MODULE: + case VerilatedTracePrefixType::SCOPE_MODULE: + case VerilatedTracePrefixType::SCOPE_INTERFACE: + case VerilatedTracePrefixType::STRUCT_PACKED: + case VerilatedTracePrefixType::STRUCT_UNPACKED: + case VerilatedTracePrefixType::UNION_PACKED: + break; + default: break; + } + m_prefixStack.pop_back(); + assert(!m_prefixStack.empty()); // Always one left, the constructor's initial one +} + +void VerilatedSaif::declare(uint32_t code, const char* name, const char* wirep, bool array, + int arraynum, bool bussed, int msb, int lsb) { + if (code >= m_activity.size()) m_codeToActivity.resize(code + 1); + 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 (m_suffixes.size() <= nextCode() * VL_TRACE_SUFFIX_ENTRY_SIZE) { + m_suffixes.resize(nextCode() * VL_TRACE_SUFFIX_ENTRY_SIZE * 2, 0); + } + + // Keep upper bound on bytes a single signal can emit into the buffer + m_maxSignalBytes = std::max(m_maxSignalBytes, bits + 32); + // Make sure write buffer is large enough, plus header + bufferResize(m_maxSignalBytes + 1024); + + if (!enabled) return; + + const size_t block_size = 1024; + if (m_activityArena.empty() + || m_activityArena.back().size() + bits > m_activityArena.back().capacity()) { + m_activityArena.emplace_back(); + m_activityArena.back().reserve(block_size); + } + size_t bitsIdx = m_activityArena.back().size(); + m_activityArena.back().resize(m_activityArena.back().size() + bits); + m_codeToActivity[code] = m_activity.size(); + m_activity.push_back({ + .name = name, + .lsb = (uint32_t) lsb, + .width = (uint32_t) bits, + .bits = m_activityArena.back().data() + bitsIdx, + }); +} + +void VerilatedSaif::declEvent(uint32_t code, uint32_t fidx, const char* name, int dtypenum, + VerilatedTraceSigDirection, VerilatedTraceSigKind, + VerilatedTraceSigType, bool array, int arraynum) { + declare(code, name, "event", array, arraynum, false, 0, 0); +} +void VerilatedSaif::declBit(uint32_t code, uint32_t fidx, const char* name, int dtypenum, + VerilatedTraceSigDirection, VerilatedTraceSigKind, + VerilatedTraceSigType, bool array, int arraynum) { + declare(code, name, "wire", array, arraynum, false, 0, 0); +} +void VerilatedSaif::declBus(uint32_t code, uint32_t fidx, const char* name, int dtypenum, + VerilatedTraceSigDirection, VerilatedTraceSigKind, + VerilatedTraceSigType, bool array, int arraynum, int msb, int lsb) { + declare(code, name, "wire", array, arraynum, true, msb, lsb); +} +void VerilatedSaif::declQuad(uint32_t code, uint32_t fidx, const char* name, int dtypenum, + VerilatedTraceSigDirection, VerilatedTraceSigKind, + VerilatedTraceSigType, bool array, int arraynum, int msb, int lsb) { + declare(code, name, "wire", array, arraynum, true, msb, lsb); +} +void VerilatedSaif::declArray(uint32_t code, uint32_t fidx, const char* name, int dtypenum, + VerilatedTraceSigDirection, VerilatedTraceSigKind, + VerilatedTraceSigType, bool array, int arraynum, int msb, int lsb) { + declare(code, name, "wire", array, arraynum, true, msb, lsb); +} +void VerilatedSaif::declDouble(uint32_t code, uint32_t fidx, const char* name, int dtypenum, + VerilatedTraceSigDirection, VerilatedTraceSigKind, + VerilatedTraceSigType, bool array, int arraynum) { + declare(code, name, "real", array, arraynum, false, 63, 0); +} + +//============================================================================= +// Get/commit trace buffer + +VerilatedSaif::Buffer* VerilatedSaif::getTraceBuffer(uint32_t fidx) { + return new Buffer{*this}; +} + +void VerilatedSaif::commitTraceBuffer(VerilatedSaif::Buffer* bufp) { + delete bufp; +} + +//============================================================================= +// VerilatedSaifBuffer implementation + +//============================================================================= +// emit* trace routines + +// 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 VerilatedSaifBuffer::emitEvent(uint32_t code) { + std::abort(); +} + +VL_ATTR_ALWINLINE +void VerilatedSaifBuffer::emitBit(uint32_t code, CData newval) { + auto& activity = m_owner.m_activity[m_owner.m_codeToActivity[code]]; + auto& bit = activity.bits[0]; + bit.aggregateVal(m_owner.m_time - activity.lastTime, newval); + activity.lastTime = m_owner.m_time; +} + +VL_ATTR_ALWINLINE +void VerilatedSaifBuffer::emitCData(uint32_t code, CData newval, int bits) { + auto& activity = m_owner.m_activity[m_owner.m_codeToActivity[code]]; + assert(bits <= activity.width); + auto dt = m_owner.m_time - activity.lastTime; + for (size_t i = 0; i < activity.width; i++) { + activity.bits[i].aggregateVal(dt, (newval >> i) & 1); + } + activity.lastTime = m_owner.m_time; +} + +VL_ATTR_ALWINLINE +void VerilatedSaifBuffer::emitSData(uint32_t code, SData newval, int bits) { + auto& activity = m_owner.m_activity[m_owner.m_codeToActivity[code]]; + assert(bits <= activity.width); + auto dt = m_owner.m_time - activity.lastTime; + for (size_t i = 0; i < activity.width; i++) { + activity.bits[i].aggregateVal(dt, (newval >> i) & 1); + } + activity.lastTime = m_owner.m_time; +} + +VL_ATTR_ALWINLINE +void VerilatedSaifBuffer::emitIData(uint32_t code, IData newval, int bits) { + auto& activity = m_owner.m_activity[m_owner.m_codeToActivity[code]]; + assert(bits <= activity.width); + auto dt = m_owner.m_time - activity.lastTime; + for (size_t i = 0; i < activity.width; i++) { + activity.bits[i].aggregateVal(dt, (newval >> i) & 1); + } + activity.lastTime = m_owner.m_time; +} + +VL_ATTR_ALWINLINE +void VerilatedSaifBuffer::emitQData(uint32_t code, QData newval, int bits) { + auto& activity = m_owner.m_activity[m_owner.m_codeToActivity[code]]; + assert(bits <= activity.width); + auto dt = m_owner.m_time - activity.lastTime; + for (size_t i = 0; i < activity.width; i++) { + activity.bits[i].aggregateVal(dt, (newval >> i) & 1); + } + activity.lastTime = m_owner.m_time; +} + +VL_ATTR_ALWINLINE +void VerilatedSaifBuffer::emitWData(uint32_t code, const WData* newvalp, int bits) { + const int bitsInMSW = VL_BITBIT_E(bits) ? VL_BITBIT_E(bits) : VL_EDATASIZE; + auto& activity = m_owner.m_activity[m_owner.m_codeToActivity[code]]; + assert(bits <= activity.width); + auto dt = m_owner.m_time - activity.lastTime; + for (size_t i = bitsInMSW; i < bitsInMSW; i++) { + activity.bits[i].aggregateVal(dt, (newvalp[0] >> VL_BITBIT_E(i)) & 1); + } + for (size_t i = bitsInMSW; i < activity.width; i++) { + size_t j = VL_WORDS_I(i); + activity.bits[i].aggregateVal(dt, (newvalp[j] >> VL_BITBIT_E(i)) & 1); + } + activity.lastTime = m_owner.m_time; +} + +VL_ATTR_ALWINLINE +void VerilatedSaifBuffer::emitDouble(uint32_t code, double newval) { + std::abort(); +} diff --git a/include/verilated_saif_c.h b/include/verilated_saif_c.h new file mode 100644 index 000000000..fb145d5a4 --- /dev/null +++ b/include/verilated_saif_c.h @@ -0,0 +1,348 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// Copyright 2001-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 tracing in SAIF format header +/// +/// User wrapper code should use this header when creating SAIF traces. +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_SAIF_C_H_ +#define VERILATOR_VERILATED_SAIF_C_H_ + +#include "verilated.h" +#include "verilated_trace.h" + +#include +#include + +class VerilatedSaifBuffer; +class VerilatedSaifFile; +struct ActivityBit { + bool lastVal = false; + uint64_t highTime = 0; + size_t transitions = 0; + + VL_ATTR_ALWINLINE + void aggregateVal(uint64_t dt, bool newVal) { + transitions += newVal != lastVal ? 1 : 0; + highTime += lastVal ? dt : 0; + lastVal = newVal; + } +}; +struct ActivityVar { + const char* name; + uint32_t lsb; + uint32_t width; + ActivityBit* bits; + uint64_t lastTime = 0; +}; + +//============================================================================= +// VerilatedSaif +// Base class to create a Verilator SAIF dump +// This is an internally used class - see VerilatedSaifC for what to call from applications + +class VerilatedSaif VL_NOT_FINAL : public VerilatedTrace { +public: + using Super = VerilatedTrace; + +private: + friend VerilatedSaifBuffer; // Give the buffer access to the private bits + + //========================================================================= + // SAIF-specific internals + + VerilatedSaifFile* m_filep; // File we're writing to + bool m_fileNewed; // m_filep needs destruction + bool m_isOpen = false; // True indicates open file + std::string m_filename; // Filename we're writing to (if open) + uint64_t m_rolloverSize = 0; // File size to rollover at + int m_indent = 0; // Indentation depth + + char* m_wrBufp; // Output buffer + char* m_wrFlushp; // Output buffer flush trigger location + char* m_writep; // Write pointer into output buffer + size_t m_wrChunkSize; // Output buffer size + size_t m_maxSignalBytes = 0; // Upper bound on number of bytes a single signal can generate + uint64_t m_wroteBytes = 0; // Number of bytes written to this file + + std::vector m_suffixes; // SAIF line end string codes + metadata + std::vector m_activity; + std::vector m_codeToActivity; + std::vector> m_activityArena; + uint64_t m_time; + + // Prefixes to add to signal names/scope types + std::vector> m_prefixStack{ + {"", VerilatedTracePrefixType::SCOPE_MODULE}}; + + // Vector of free trace buffers as (pointer, size) pairs. + std::vector> m_freeBuffers; + size_t m_numBuffers = 0; // Number of trace buffers allocated + + void bufferResize(size_t minsize); + void bufferFlush() VL_MT_UNSAFE_ONE; + void bufferCheck() { + // Flush the write buffer if there's not enough space left for new information + // We only call this once per vector, so we need enough slop for a very wide "b###" line + if (VL_UNLIKELY(m_writep > m_wrFlushp)) bufferFlush(); + } + void openNextImp(bool incFilename); + void closePrev(); + void closeErr(); + void printIndent(int level_change); + void printStr(const char* str); + void declare(uint32_t code, const char* name, const char* wirep, bool array, int arraynum, + bool bussed, int msb, int lsb); + + // CONSTRUCTORS + VL_UNCOPYABLE(VerilatedSaif); + +protected: + //========================================================================= + // Implementation of VerilatedTrace interface + + // Called when the trace moves forward to a new time point + void emitTimeChange(uint64_t timeui) override; + + // Hooks called from VerilatedTrace + bool preFullDump() override { return isOpen(); } + bool preChangeDump() override; + + // Trace buffer management + Buffer* getTraceBuffer(uint32_t fidx) override; + void commitTraceBuffer(Buffer*) override; + + // Configure sub-class + void configure(const VerilatedTraceConfig&) override {}; + +public: + //========================================================================= + // External interface to client code + + // CONSTRUCTOR + explicit VerilatedSaif(VerilatedSaifFile* filep = nullptr); + ~VerilatedSaif(); + + // ACCESSORS + // Set size in bytes after which new file should be created. + void rolloverSize(uint64_t size) VL_MT_SAFE { m_rolloverSize = size; } + + // METHODS - All must be thread safe + // Open the file; call isOpen() to see if errors + void open(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex); + // Open next data-only file + void openNext(bool incFilename) VL_MT_SAFE_EXCLUDES(m_mutex); + // Close the file + void close() VL_MT_SAFE_EXCLUDES(m_mutex); + // Flush any remaining data to this file + void flush() VL_MT_SAFE_EXCLUDES(m_mutex); + // Return if file is open + bool isOpen() const VL_MT_SAFE { return m_isOpen; } + + //========================================================================= + // Internal interface to Verilator generated code + + 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); +}; + +#ifndef DOXYGEN +// Declare specialization here as it's used in VerilatedFstC just below +template <> +void VerilatedSaif::Super::dump(uint64_t time); +template <> +void VerilatedSaif::Super::set_time_unit(const char* unitp); +template <> +void VerilatedSaif::Super::set_time_unit(const std::string& unit); +template <> +void VerilatedSaif::Super::set_time_resolution(const char* unitp); +template <> +void VerilatedSaif::Super::set_time_resolution(const std::string& unit); +template <> +void VerilatedSaif::Super::dumpvars(int level, const std::string& hier); +#endif // DOXYGEN + +//============================================================================= +// VerilatedSaifBuffer + +class VerilatedSaifBuffer VL_NOT_FINAL { + // Give the trace file and sub-classes access to the private bits + friend VerilatedSaif; + friend VerilatedSaif::Super; + friend VerilatedSaif::Buffer; + friend VerilatedSaif::OffloadBuffer; + + VerilatedSaif& m_owner; // Trace file owning this buffer. Required by subclasses. + + // Write pointer into output buffer (in parallel mode, this is set up in 'getTraceBuffer') + char* m_writep = m_owner.parallel() ? nullptr : m_owner.m_writep; + // Output buffer flush trigger location (only used when not parallel) + char* const m_wrFlushp = m_owner.parallel() ? nullptr : m_owner.m_wrFlushp; + + // SAIF line end string codes + metadata + const char* const m_suffixes = m_owner.m_suffixes.data(); + // The maximum number of bytes a single signal can emit + const size_t m_maxSignalBytes = m_owner.m_maxSignalBytes; + + // Additional data for parallel tracing only + char* m_bufp = nullptr; // The beginning of the trace buffer + size_t m_size = 0; // The size of the buffer at m_bufp + char* m_growp = nullptr; // Resize limit pointer + + void adjustGrowp() { + m_growp = (m_bufp + m_size) - (2 * m_maxSignalBytes); + assert(m_growp >= m_bufp + m_maxSignalBytes); + } + + // CONSTRUCTOR + explicit VerilatedSaifBuffer(VerilatedSaif& owner) + : m_owner{owner} {} + virtual ~VerilatedSaifBuffer() = default; + + //========================================================================= + // Implementation of VerilatedTraceBuffer interface + // Implementations of duck-typed methods for VerilatedTraceBuffer. These are + // called from only one place (the full* methods), so always inline them. + VL_ATTR_ALWINLINE void emitEvent(uint32_t code); + VL_ATTR_ALWINLINE void emitBit(uint32_t code, CData newval); + VL_ATTR_ALWINLINE void emitCData(uint32_t code, CData newval, int bits); + VL_ATTR_ALWINLINE void emitSData(uint32_t code, SData newval, int bits); + VL_ATTR_ALWINLINE void emitIData(uint32_t code, IData newval, int bits); + VL_ATTR_ALWINLINE void emitQData(uint32_t code, QData newval, int bits); + VL_ATTR_ALWINLINE void emitWData(uint32_t code, const WData* newvalp, int bits); + VL_ATTR_ALWINLINE void emitDouble(uint32_t code, double newval); +}; + +//============================================================================= +// VerilatedFile +/// Class representing a file to write to. These virtual methods can be +/// overrode for e.g. socket I/O. + +class VerilatedSaifFile VL_NOT_FINAL { +private: + int m_fd = 0; // File descriptor we're writing to +public: + // METHODS + /// Construct a (as yet) closed file + VerilatedSaifFile() = default; + /// Close and destruct + virtual ~VerilatedSaifFile() = default; + /// Open a file with given filename + virtual bool open(const std::string& name) VL_MT_UNSAFE; + /// Close object's file + virtual void close() VL_MT_UNSAFE; + /// Write data to file (if it is open) + virtual ssize_t write(const char* bufp, ssize_t len) VL_MT_UNSAFE; +}; + +//============================================================================= +// VerilatedSaifC +/// Class representing a SAIF dump file in C standalone (no SystemC) +/// simulations. Also derived for use in SystemC simulations. + +class VerilatedSaifC VL_NOT_FINAL : public VerilatedTraceBaseC { + VerilatedSaif m_sptrace; // Trace file being created + + // CONSTRUCTORS + VL_UNCOPYABLE(VerilatedSaifC); + +public: + /// Construct the dump. Optional argument is a preconstructed file. + explicit VerilatedSaifC(VerilatedSaifFile* filep = nullptr) + : m_sptrace{filep} {} + /// Destruct, flush, and close the dump + virtual ~VerilatedSaifC() { close(); } + + // METHODS - User called + + /// Return if file is open + bool isOpen() const override VL_MT_SAFE { return m_sptrace.isOpen(); } + /// Open a new SAIF file + /// This includes a complete header dump each time it is called, + /// just as if this object was deleted and reconstructed. + virtual void open(const char* filename) VL_MT_SAFE { m_sptrace.open(filename); } + /// Continue a SAIF dump by rotating to a new file name + /// The header is only in the first file created, this allows + /// "cat" to be used to combine the header plus any number of data files. + void openNext(bool incFilename = true) VL_MT_SAFE { m_sptrace.openNext(incFilename); } + /// Set size in bytes after which new file should be created + /// This will create a header file, followed by each separate file + /// which might be larger than the given size (due to chunking and + /// alignment to a start of a given time's dump). Any file but the + /// first may be removed. Cat files together to create viewable saif. + void rolloverSize(size_t size) VL_MT_SAFE { m_sptrace.rolloverSize(size); } + /// Close dump + void close() VL_MT_SAFE { + m_sptrace.close(); + modelConnected(false); + } + /// Flush dump + void flush() VL_MT_SAFE { m_sptrace.flush(); } + /// Write one cycle of dump data + /// Call with the current context's time just after eval'ed, + /// e.g. ->dump(contextp->time()) + void dump(uint64_t timeui) VL_MT_SAFE { m_sptrace.dump(timeui); } + /// Write one cycle of dump data - backward compatible and to reduce + /// conversion warnings. It's better to use a uint64_t time instead. + void dump(double timestamp) { dump(static_cast(timestamp)); } + void dump(uint32_t timestamp) { dump(static_cast(timestamp)); } + void dump(int timestamp) { dump(static_cast(timestamp)); } + + // METHODS - Internal/backward compatible + // \protectedsection + + // Set time units (s/ms, defaults to ns) + // Users should not need to call this, as for Verilated models, these + // propagate from the Verilated default timeunit + void set_time_unit(const char* unit) VL_MT_SAFE { m_sptrace.set_time_unit(unit); } + void set_time_unit(const std::string& unit) VL_MT_SAFE { m_sptrace.set_time_unit(unit); } + // Set time resolution (s/ms, defaults to ns) + // Users should not need to call this, as for Verilated models, these + // propagate from the Verilated default timeprecision + void set_time_resolution(const char* unit) VL_MT_SAFE { m_sptrace.set_time_resolution(unit); } + void set_time_resolution(const std::string& unit) VL_MT_SAFE { + m_sptrace.set_time_resolution(unit); + } + // Set variables to dump, using $dumpvars format + // If level = 0, dump everything and hier is then ignored + void dumpvars(int level, const std::string& hier) VL_MT_SAFE { + m_sptrace.dumpvars(level, hier); + } + + // Internal class access + VerilatedSaif* spTrace() { return &m_sptrace; } +}; + +#endif // guard diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index 71d1979a6..3f44a9069 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -563,6 +563,10 @@ public: of.puts("VM_TRACE_FST = "); of.puts(v3Global.opt.trace() && v3Global.opt.traceFormat().fst() ? "1" : "0"); of.puts("\n"); + of.puts("# Tracing output mode in SAIF format? 0/1 (from --trace-saif)\n"); + of.puts("VM_TRACE_SAIF = "); + of.puts(v3Global.opt.trace() && v3Global.opt.traceFormat().saif() ? "1" : "0"); + of.puts("\n"); of.puts("\n### Object file lists...\n"); for (int support = 0; support < 3; ++support) { diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 4170c13e1..04dab7502 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1640,6 +1640,10 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-top", Set, &m_topModule); DECL_OPTION("-top-module", Set, &m_topModule); DECL_OPTION("-trace", OnOff, &m_trace); + DECL_OPTION("-trace-saif", CbCall, [this]() { + m_trace = true; + m_traceFormat = TraceFormat::SAIF; + }); DECL_OPTION("-trace-coverage", OnOff, &m_traceCoverage); DECL_OPTION("-trace-depth", Set, &m_traceDepth); DECL_OPTION("-trace-fst", CbCall, [this]() { diff --git a/src/V3Options.h b/src/V3Options.h index fc38708f7..194283c22 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -133,7 +133,7 @@ inline std::ostream& operator<<(std::ostream& os, const VTimescale& rhs) { class TraceFormat final { public: - enum en : uint8_t { VCD = 0, FST } m_e; + enum en : uint8_t { VCD = 0, FST, SAIF } m_e; // cppcheck-suppress noExplicitConstructor constexpr TraceFormat(en _e = VCD) : m_e{_e} {} @@ -141,13 +141,14 @@ public: : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning constexpr operator en() const { return m_e; } bool fst() const { return m_e == FST; } + bool saif() const { return m_e == SAIF; } bool vcd() const { return m_e == VCD; } string classBase() const VL_MT_SAFE { - static const char* const names[] = {"VerilatedVcd", "VerilatedFst"}; + static const char* const names[] = {"VerilatedVcd", "VerilatedFst", "VerilatedSaif"}; return names[m_e]; } string sourceName() const VL_MT_SAFE { - static const char* const names[] = {"verilated_vcd", "verilated_fst"}; + static const char* const names[] = {"verilated_vcd", "verilated_fst", "verilated_saif"}; return names[m_e]; } };