mirror of
https://github.com/verilator/verilator.git
synced 2025-01-06 06:37:45 +00:00
95c4ade718
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.
654 lines
23 KiB
C++
654 lines
23 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//=============================================================================
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
// Copyright 2001-2023 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 VCD 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_vcd_c.h"
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <fcntl.h>
|
|
|
|
#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 VCD 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_VCD_CODE_SIZE = 5; // Maximum length of a VCD string code
|
|
|
|
// We use 8 bytes per code in a suffix buffer array.
|
|
// 1 byte optional separator + VL_TRACE_MAX_VCD_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 VerilatedVcd
|
|
#define VL_BUF_T VerilatedVcdBuffer
|
|
#include "verilated_trace_imp.h"
|
|
#undef VL_SUB_T
|
|
#undef VL_BUF_T
|
|
|
|
//=============================================================================
|
|
//=============================================================================
|
|
//=============================================================================
|
|
// VerilatedVcdFile
|
|
|
|
bool VerilatedVcdFile::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 VerilatedVcdFile::close() VL_MT_UNSAFE { ::close(m_fd); }
|
|
|
|
ssize_t VerilatedVcdFile::write(const char* bufp, ssize_t len) VL_MT_UNSAFE {
|
|
return ::write(m_fd, bufp, len);
|
|
}
|
|
|
|
//=============================================================================
|
|
//=============================================================================
|
|
//=============================================================================
|
|
// Opening/Closing
|
|
|
|
VerilatedVcd::VerilatedVcd(VerilatedVcdFile* 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 VerilatedVcdFile : filep;
|
|
m_wrChunkSize = 8 * 1024;
|
|
m_wrBufp = new char[m_wrChunkSize * 8];
|
|
m_wrFlushp = m_wrBufp + m_wrChunkSize * 6;
|
|
m_writep = m_wrBufp;
|
|
}
|
|
|
|
void VerilatedVcd::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("$version Generated by VerilatedVcd $end\n");
|
|
printStr("$timescale ");
|
|
printStr(timeResStr().c_str()); // lintok-begin-on-ref
|
|
printStr(" $end\n");
|
|
|
|
// Scope and signal definitions
|
|
assert(m_indent == 0);
|
|
++m_indent;
|
|
Super::traceInit();
|
|
--m_indent;
|
|
assert(m_indent == 0);
|
|
|
|
printStr("$enddefinitions $end\n\n\n");
|
|
|
|
// When using rollover, the first chunk contains the header only.
|
|
if (m_rolloverSize) openNextImp(true);
|
|
}
|
|
|
|
void VerilatedVcd::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 VerilatedVcd::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 VerilatedVcd::preChangeDump() {
|
|
if (VL_UNLIKELY(m_rolloverSize && m_wroteBytes > m_rolloverSize)) openNextImp(true);
|
|
return isOpen();
|
|
}
|
|
|
|
void VerilatedVcd::emitTimeChange(uint64_t timeui) {
|
|
printStr("#");
|
|
const std::string str = std::to_string(timeui);
|
|
printStr(str.c_str());
|
|
printStr("\n");
|
|
}
|
|
|
|
VerilatedVcd::~VerilatedVcd() {
|
|
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 VerilatedVcd::closePrev() {
|
|
// This function is on the flush() call path
|
|
if (!isOpen()) return;
|
|
|
|
Super::flushBase();
|
|
bufferFlush();
|
|
m_isOpen = false;
|
|
m_filep->close();
|
|
}
|
|
|
|
void VerilatedVcd::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 VerilatedVcd::close() VL_MT_SAFE_EXCLUDES(m_mutex) {
|
|
// 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 VerilatedVcd::flush() VL_MT_SAFE_EXCLUDES(m_mutex) {
|
|
const VerilatedLockGuard lock{m_mutex};
|
|
Super::flushBase();
|
|
bufferFlush();
|
|
}
|
|
|
|
void VerilatedVcd::printStr(const char* str) {
|
|
// Not fast...
|
|
while (*str) {
|
|
*m_writep++ = *str++;
|
|
bufferCheck();
|
|
}
|
|
}
|
|
|
|
void VerilatedVcd::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 VerilatedVcd::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(!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
|
|
= std::string{"VerilatedVcd::bufferFlush: "} + std::strerror(errno);
|
|
VL_FATAL_MT("", 0, "", msg.c_str());
|
|
closeErr();
|
|
break;
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset buffer
|
|
m_writep = m_wrBufp;
|
|
}
|
|
|
|
//=============================================================================
|
|
// Definitions
|
|
|
|
void VerilatedVcd::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 VerilatedVcd::pushPrefix(const std::string& name, VerilatedTracePrefixType type) {
|
|
std::string newPrefix = m_prefixStack.back().first + name;
|
|
switch (type) {
|
|
case VerilatedTracePrefixType::SCOPE_MODULE:
|
|
case VerilatedTracePrefixType::SCOPE_INTERFACE:
|
|
case VerilatedTracePrefixType::STRUCT_PACKED:
|
|
case VerilatedTracePrefixType::STRUCT_UNPACKED:
|
|
case VerilatedTracePrefixType::UNION_PACKED: {
|
|
printIndent(1);
|
|
printStr("$scope module ");
|
|
const std::string n = lastWord(newPrefix);
|
|
printStr(n.c_str());
|
|
printStr(" $end\n");
|
|
newPrefix += ' ';
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
m_prefixStack.emplace_back(newPrefix, type);
|
|
}
|
|
|
|
void VerilatedVcd::popPrefix() {
|
|
switch (m_prefixStack.back().second) {
|
|
case VerilatedTracePrefixType::SCOPE_MODULE:
|
|
case VerilatedTracePrefixType::SCOPE_INTERFACE:
|
|
case VerilatedTracePrefixType::STRUCT_PACKED:
|
|
case VerilatedTracePrefixType::STRUCT_UNPACKED:
|
|
case VerilatedTracePrefixType::UNION_PACKED:
|
|
printIndent(-1);
|
|
printStr("$upscope $end\n");
|
|
break;
|
|
default: break;
|
|
}
|
|
m_prefixStack.pop_back();
|
|
assert(!m_prefixStack.empty());
|
|
}
|
|
|
|
void VerilatedVcd::declare(uint32_t code, const char* name, const char* wirep, 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 (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;
|
|
|
|
// Create the VCD code and build the suffix array entry
|
|
char vcdCode[VL_TRACE_SUFFIX_ENTRY_SIZE];
|
|
{
|
|
// Render the VCD code
|
|
char* vcdCodeWritep = vcdCode;
|
|
uint32_t codeEnc = code;
|
|
do {
|
|
*vcdCodeWritep++ = static_cast<char>('!' + codeEnc % 94);
|
|
codeEnc /= 94;
|
|
} while (codeEnc--);
|
|
*vcdCodeWritep = '\0';
|
|
const size_t vcdCodeLength = vcdCodeWritep - vcdCode;
|
|
assert(vcdCodeLength <= VL_TRACE_MAX_VCD_CODE_SIZE);
|
|
// Build suffix array entry
|
|
char* const entryBeginp = &m_suffixes[code * VL_TRACE_SUFFIX_ENTRY_SIZE];
|
|
entryBeginp[0] = ' '; // Separator
|
|
// 1 bit values don't have a ' ' separator between value and string code
|
|
char* entryWritep = bits == 1 ? entryBeginp : entryBeginp + 1;
|
|
// Use memcpy as we know the size, and strcpy is flagged unsafe
|
|
std::memcpy(entryWritep, vcdCode, vcdCodeLength);
|
|
entryWritep += vcdCodeLength;
|
|
// Line terminator
|
|
*entryWritep++ = '\n';
|
|
// Set length of suffix (used to increment write pointer)
|
|
assert(entryWritep <= entryBeginp + VL_TRACE_SUFFIX_ENTRY_SIZE - 1);
|
|
entryBeginp[VL_TRACE_SUFFIX_ENTRY_SIZE - 1] = static_cast<char>(entryWritep - entryBeginp);
|
|
}
|
|
|
|
// Assemble the declaration
|
|
std::string decl = "$var ";
|
|
decl += wirep;
|
|
decl += ' ';
|
|
decl += std::to_string(bits);
|
|
decl += ' ';
|
|
decl += vcdCode;
|
|
decl += ' ';
|
|
decl += lastWord(hierarchicalName);
|
|
if (array) {
|
|
decl += '[';
|
|
decl += std::to_string(arraynum);
|
|
decl += ']';
|
|
}
|
|
if (bussed) {
|
|
decl += " [";
|
|
decl += std::to_string(msb);
|
|
decl += ':';
|
|
decl += std::to_string(lsb);
|
|
decl += ']';
|
|
}
|
|
decl += " $end\n";
|
|
printIndent(0);
|
|
printStr(decl.c_str());
|
|
}
|
|
|
|
void VerilatedVcd::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 VerilatedVcd::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 VerilatedVcd::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 VerilatedVcd::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 VerilatedVcd::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 VerilatedVcd::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
|
|
|
|
VerilatedVcd::Buffer* VerilatedVcd::getTraceBuffer(uint32_t fidx) {
|
|
VerilatedVcd::Buffer* const bufp = new Buffer{*this};
|
|
if (parallel()) {
|
|
// Note: This is called from VerilatedVcd::dump, which already holds the lock
|
|
// If no buffer available, allocate a new one
|
|
if (m_freeBuffers.empty()) {
|
|
constexpr size_t pageSize = 4096;
|
|
// 4 * m_maxSignalBytes, so we can reserve 2 * m_maxSignalBytes at the end for safety
|
|
size_t startingSize = roundUpToMultipleOf<pageSize>(4 * m_maxSignalBytes);
|
|
m_freeBuffers.emplace_back(new char[startingSize], startingSize);
|
|
++m_numBuffers;
|
|
}
|
|
// Grab a buffer
|
|
const auto pair = m_freeBuffers.back();
|
|
m_freeBuffers.pop_back();
|
|
// Initialize
|
|
bufp->m_writep = bufp->m_bufp = pair.first;
|
|
bufp->m_size = pair.second;
|
|
bufp->adjustGrowp();
|
|
}
|
|
// Return the buffer
|
|
return bufp;
|
|
}
|
|
|
|
void VerilatedVcd::commitTraceBuffer(VerilatedVcd::Buffer* bufp) {
|
|
if (parallel()) {
|
|
// Note: This is called from VerilatedVcd::dump, which already holds the lock
|
|
// Resize output buffer. Note, we use the full size of the trace buffer, as
|
|
// this is a lot more stable than the actual occupancy of the trace buffer.
|
|
// This helps us to avoid re-allocations due to small size changes.
|
|
bufferResize(bufp->m_size);
|
|
// Compute occupancy of buffer
|
|
const size_t usedSize = bufp->m_writep - bufp->m_bufp;
|
|
// Copy to output buffer
|
|
std::memcpy(m_writep, bufp->m_bufp, usedSize);
|
|
// Adjust write pointer
|
|
m_writep += usedSize;
|
|
// Flush if necessary
|
|
bufferCheck();
|
|
// Put buffer back on free list
|
|
m_freeBuffers.emplace_back(bufp->m_bufp, bufp->m_size);
|
|
} else {
|
|
// Needs adjusting for emitTimeChange
|
|
m_writep = bufp->m_writep;
|
|
}
|
|
delete bufp;
|
|
}
|
|
|
|
//=============================================================================
|
|
// VerilatedVcdBuffer implementation
|
|
|
|
//=============================================================================
|
|
// Trace rendering primitives
|
|
|
|
static void VerilatedVcdCCopyAndAppendNewLine(char* writep,
|
|
const char* suffixp) VL_ATTR_NO_SANITIZE_ALIGN;
|
|
|
|
static void VerilatedVcdCCopyAndAppendNewLine(char* writep, const char* suffixp) {
|
|
// Copy the whole suffix (this avoid having hard to predict branches which
|
|
// helps a lot). Note: The maximum length of the suffix is
|
|
// VL_TRACE_MAX_VCD_CODE_SIZE + 2 == 7, but we unroll this here for speed.
|
|
#ifdef VL_X86_64
|
|
// Copy the whole 8 bytes in one go, this works on little-endian machines
|
|
// supporting unaligned stores.
|
|
*reinterpret_cast<uint64_t*>(writep) = *reinterpret_cast<const uint64_t*>(suffixp);
|
|
#else
|
|
// Portable variant
|
|
writep[0] = suffixp[0];
|
|
writep[1] = suffixp[1];
|
|
writep[2] = suffixp[2];
|
|
writep[3] = suffixp[3];
|
|
writep[4] = suffixp[4];
|
|
writep[5] = suffixp[5];
|
|
writep[6] = '\n'; // The 6th index is always '\n' if it's relevant, no need to fetch it.
|
|
#endif
|
|
}
|
|
|
|
void VerilatedVcdBuffer::finishLine(uint32_t code, char* writep) {
|
|
const char* const suffixp = m_suffixes + code * VL_TRACE_SUFFIX_ENTRY_SIZE;
|
|
VL_DEBUG_IFDEF(assert(suffixp[0]););
|
|
VerilatedVcdCCopyAndAppendNewLine(writep, suffixp);
|
|
|
|
// Now write back the write pointer incremented by the actual size of the
|
|
// suffix, which was stored in the last byte of the suffix buffer entry.
|
|
m_writep = writep + suffixp[VL_TRACE_SUFFIX_ENTRY_SIZE - 1];
|
|
|
|
if (m_owner.parallel()) {
|
|
// Double the size of the buffer if necessary
|
|
if (VL_UNLIKELY(m_writep >= m_growp)) {
|
|
// Compute occupied size of current buffer
|
|
const size_t usedSize = m_writep - m_bufp;
|
|
// We are always doubling the size
|
|
m_size *= 2;
|
|
// Allocate the new buffer
|
|
char* const newBufp = new char[m_size];
|
|
// Copy from current buffer to new buffer
|
|
std::memcpy(newBufp, m_bufp, usedSize);
|
|
// Delete current buffer
|
|
delete[] m_bufp;
|
|
// Make new buffer the current buffer
|
|
m_bufp = newBufp;
|
|
// Adjust write pointer
|
|
m_writep = m_bufp + usedSize;
|
|
// Adjust resize limit
|
|
adjustGrowp();
|
|
}
|
|
} else {
|
|
// 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)) {
|
|
m_owner.m_writep = m_writep;
|
|
m_owner.bufferFlush();
|
|
m_writep = m_owner.m_writep;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
// 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 VerilatedVcdBuffer::emitEvent(uint32_t code, VlEvent newval) {
|
|
const bool triggered = newval.isTriggered();
|
|
// TODO : It seems that untriggered events are not filtered
|
|
// should be tested before this last step
|
|
if (triggered) {
|
|
// Don't prefetch suffix as it's a bit too late;
|
|
char* wp = m_writep;
|
|
*wp++ = '1';
|
|
finishLine(code, wp);
|
|
}
|
|
}
|
|
|
|
VL_ATTR_ALWINLINE
|
|
void VerilatedVcdBuffer::emitBit(uint32_t code, CData newval) {
|
|
// Don't prefetch suffix as it's a bit too late;
|
|
char* wp = m_writep;
|
|
*wp++ = '0' | static_cast<char>(newval);
|
|
finishLine(code, wp);
|
|
}
|
|
|
|
VL_ATTR_ALWINLINE
|
|
void VerilatedVcdBuffer::emitCData(uint32_t code, CData newval, int bits) {
|
|
char* wp = m_writep;
|
|
*wp++ = 'b';
|
|
cvtCDataToStr(wp, newval << (VL_BYTESIZE - bits));
|
|
finishLine(code, wp + bits);
|
|
}
|
|
|
|
VL_ATTR_ALWINLINE
|
|
void VerilatedVcdBuffer::emitSData(uint32_t code, SData newval, int bits) {
|
|
char* wp = m_writep;
|
|
*wp++ = 'b';
|
|
cvtSDataToStr(wp, newval << (VL_SHORTSIZE - bits));
|
|
finishLine(code, wp + bits);
|
|
}
|
|
|
|
VL_ATTR_ALWINLINE
|
|
void VerilatedVcdBuffer::emitIData(uint32_t code, IData newval, int bits) {
|
|
char* wp = m_writep;
|
|
*wp++ = 'b';
|
|
cvtIDataToStr(wp, newval << (VL_IDATASIZE - bits));
|
|
finishLine(code, wp + bits);
|
|
}
|
|
|
|
VL_ATTR_ALWINLINE
|
|
void VerilatedVcdBuffer::emitQData(uint32_t code, QData newval, int bits) {
|
|
char* wp = m_writep;
|
|
*wp++ = 'b';
|
|
cvtQDataToStr(wp, newval << (VL_QUADSIZE - bits));
|
|
finishLine(code, wp + bits);
|
|
}
|
|
|
|
VL_ATTR_ALWINLINE
|
|
void VerilatedVcdBuffer::emitWData(uint32_t code, const WData* newvalp, int bits) {
|
|
int words = VL_WORDS_I(bits);
|
|
char* wp = m_writep;
|
|
*wp++ = 'b';
|
|
// Handle the most significant word
|
|
const int bitsInMSW = VL_BITBIT_E(bits) ? VL_BITBIT_E(bits) : VL_EDATASIZE;
|
|
cvtEDataToStr(wp, newvalp[--words] << (VL_EDATASIZE - bitsInMSW));
|
|
wp += bitsInMSW;
|
|
// Handle the remaining words
|
|
while (words > 0) {
|
|
cvtEDataToStr(wp, newvalp[--words]);
|
|
wp += VL_EDATASIZE;
|
|
}
|
|
finishLine(code, wp);
|
|
}
|
|
|
|
VL_ATTR_ALWINLINE
|
|
void VerilatedVcdBuffer::emitDouble(uint32_t code, double newval) {
|
|
char* wp = m_writep;
|
|
// Buffer can't overflow before VL_SNPRINTF; we sized during declaration
|
|
VL_SNPRINTF(wp, m_maxSignalBytes, "r%.16g", newval);
|
|
wp += std::strlen(wp);
|
|
finishLine(code, wp);
|
|
}
|