SAIF support

Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
This commit is contained in:
Krzysztof Bieganski 2025-01-28 14:49:44 +01:00 committed by Mateusz Gancarz
parent 27d3eb5b7b
commit 5ea2cf2938
6 changed files with 908 additions and 3 deletions

View File

@ -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

View File

@ -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 <algorithm>
#include <cerrno>
#include <fcntl.h>
#include <string>
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
# include <io.h>
#else
# include <unistd.h>
#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<size_t>(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();
}

348
include/verilated_saif_c.h Normal file
View File

@ -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 <string>
#include <vector>
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<VerilatedSaif, VerilatedSaifBuffer> {
public:
using Super = VerilatedTrace<VerilatedSaif, VerilatedSaifBuffer>;
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<char> m_suffixes; // SAIF line end string codes + metadata
std::vector<ActivityVar> m_activity;
std::vector<uint32_t> m_codeToActivity;
std::vector<std::vector<ActivityBit>> m_activityArena;
uint64_t m_time;
// Prefixes to add to signal names/scope types
std::vector<std::pair<std::string, VerilatedTracePrefixType>> m_prefixStack{
{"", VerilatedTracePrefixType::SCOPE_MODULE}};
// Vector of free trace buffers as (pointer, size) pairs.
std::vector<std::pair<char*, size_t>> 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<uint64_t>(timestamp)); }
void dump(uint32_t timestamp) { dump(static_cast<uint64_t>(timestamp)); }
void dump(int timestamp) { dump(static_cast<uint64_t>(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

View File

@ -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) {

View File

@ -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]() {

View File

@ -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<en>(_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];
}
};