mirror of
https://github.com/verilator/verilator.git
synced 2025-01-01 04:07:34 +00:00
ff425369ac
Trace initialization (tracep->decl* functions) used to explicitly pass the complete hierarchical names of signals as string constants. This contains a lot of redundancy (path prefixes), does not scale well with large designs and resulted in .rodata sections (the string constants) in ELF executables being extremely large. This patch changes the API of trace initialization that allows pushing and popping name prefixes as we walk the hierarchy tree, which are prepended to declared signal names at run-time during trace initialization. This in turn allows us to emit repeat path/name components only once, effectively removing all duplicate path prefixes. On SweRV EH1 this reduces the .rodata section in a --trace build by 94%. Additionally, trace declarations are now emitted in lexical order by hierarchical signal names, and the top level trace initialization function respects --output-split-ctrace.
950 lines
32 KiB
C++
950 lines
32 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//=============================================================================
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
// Copyright 2001-2021 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 <ctime>
|
|
#include <fcntl.h>
|
|
|
|
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
|
|
# include <io.h>
|
|
#else
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#ifndef O_LARGEFILE // For example on WIN32
|
|
# define O_LARGEFILE 0
|
|
#endif
|
|
#ifndef O_NONBLOCK
|
|
# define O_NONBLOCK 0
|
|
#endif
|
|
#ifndef O_CLOEXEC
|
|
# 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_DERIVED_T VerilatedVcd
|
|
#include "verilated_trace_imp.cpp"
|
|
#undef VL_DERIVED_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_rolloverMB != 0);
|
|
if (!isOpen()) return;
|
|
|
|
dumpHeader();
|
|
|
|
// When using rollover, the first chunk contains the header only.
|
|
if (m_rolloverMB) 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;
|
|
fullDump(true); // First dump must be full
|
|
m_wroteBytes = 0;
|
|
}
|
|
|
|
bool VerilatedVcd::preChangeDump() {
|
|
if (VL_UNLIKELY(m_rolloverMB && m_wroteBytes > m_rolloverMB)) openNextImp(true);
|
|
return isOpen();
|
|
}
|
|
|
|
void VerilatedVcd::emitTimeChange(vluint64_t timeui) {
|
|
printStr("#");
|
|
printQuad(timeui);
|
|
printStr("\n");
|
|
}
|
|
|
|
void VerilatedVcd::makeNameMap() {
|
|
// Take signal information from each module and build m_namemapp
|
|
deleteNameMap();
|
|
m_namemapp = new NameMap;
|
|
|
|
VerilatedTrace<VerilatedVcd>::traceInit();
|
|
|
|
// Though not speced, it's illegal to generate a vcd with signals
|
|
// not under any module - it crashes at least two viewers.
|
|
// If no scope was specified, prefix everything with a "top"
|
|
// This comes from user instantiations with no name - IE Vtop("").
|
|
bool nullScope = false;
|
|
for (const auto& i : *m_namemapp) {
|
|
const std::string& hiername = i.first;
|
|
if (!hiername.empty() && hiername[0] == '\t') nullScope = true;
|
|
}
|
|
if (nullScope) {
|
|
NameMap* const newmapp = new NameMap;
|
|
for (const auto& i : *m_namemapp) {
|
|
const std::string& hiername = i.first;
|
|
const std::string& decl = i.second;
|
|
std::string newname{"top"};
|
|
if (hiername[0] != '\t') newname += ' ';
|
|
newname += hiername;
|
|
newmapp->emplace(newname, decl);
|
|
}
|
|
deleteNameMap();
|
|
m_namemapp = newmapp;
|
|
}
|
|
}
|
|
|
|
void VerilatedVcd::deleteNameMap() {
|
|
if (m_namemapp) VL_DO_CLEAR(delete m_namemapp, m_namemapp = nullptr);
|
|
}
|
|
|
|
VerilatedVcd::~VerilatedVcd() {
|
|
close();
|
|
if (m_wrBufp) VL_DO_CLEAR(delete[] m_wrBufp, m_wrBufp = nullptr);
|
|
deleteNameMap();
|
|
if (m_filep && m_fileNewed) VL_DO_CLEAR(delete m_filep, m_filep = nullptr);
|
|
}
|
|
|
|
void VerilatedVcd::closePrev() {
|
|
// This function is on the flush() call path
|
|
if (!isOpen()) return;
|
|
|
|
VerilatedTrace<VerilatedVcd>::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;
|
|
if (m_evcd) {
|
|
printStr("$vcdclose ");
|
|
printQuad(timeLastDump());
|
|
printStr(" $end\n");
|
|
}
|
|
closePrev();
|
|
// closePrev() called VerilatedTrace<VerilatedVcd>::flush(), so we just
|
|
// need to shut down the tracing thread here.
|
|
VerilatedTrace<VerilatedVcd>::closeBase();
|
|
}
|
|
|
|
void VerilatedVcd::flush() VL_MT_SAFE_EXCLUDES(m_mutex) {
|
|
const VerilatedLockGuard lock{m_mutex};
|
|
VerilatedTrace<VerilatedVcd>::flushBase();
|
|
bufferFlush();
|
|
}
|
|
|
|
void VerilatedVcd::printStr(const char* str) {
|
|
// Not fast...
|
|
while (*str) {
|
|
*m_writep++ = *str++;
|
|
bufferCheck();
|
|
}
|
|
}
|
|
|
|
void VerilatedVcd::printQuad(vluint64_t n) {
|
|
constexpr size_t LEN_STR_QUAD = 40;
|
|
char buf[LEN_STR_QUAD];
|
|
VL_SNPRINTF(buf, LEN_STR_QUAD, "%" VL_PRI64 "u", n);
|
|
printStr(buf);
|
|
}
|
|
|
|
void VerilatedVcd::bufferResize(vluint64_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 = 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 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;
|
|
}
|
|
|
|
//=============================================================================
|
|
// VCD string code
|
|
|
|
char* VerilatedVcd::writeCode(char* writep, vluint32_t code) {
|
|
*writep++ = static_cast<char>('!' + code % 94);
|
|
code /= 94;
|
|
while (code) {
|
|
--code;
|
|
*writep++ = static_cast<char>('!' + code % 94);
|
|
code /= 94;
|
|
}
|
|
return writep;
|
|
}
|
|
|
|
//=============================================================================
|
|
// Definitions
|
|
|
|
void VerilatedVcd::printIndent(int level_change) {
|
|
if (level_change < 0) m_modDepth += level_change;
|
|
assert(m_modDepth >= 0);
|
|
for (int i = 0; i < m_modDepth; i++) printStr(" ");
|
|
if (level_change > 0) m_modDepth += level_change;
|
|
}
|
|
|
|
void VerilatedVcd::dumpHeader() {
|
|
printStr("$version Generated by VerilatedVcd $end\n");
|
|
printStr("$date ");
|
|
{
|
|
const time_t tick = time(nullptr);
|
|
tm ticktm;
|
|
VL_LOCALTIME_R(&tick, &ticktm);
|
|
constexpr size_t LEN_BUF = 50;
|
|
char buf[LEN_BUF];
|
|
std::strftime(buf, LEN_BUF, "%c", &ticktm);
|
|
printStr(buf);
|
|
}
|
|
printStr(" $end\n");
|
|
|
|
printStr("$timescale ");
|
|
printStr(timeResStr().c_str()); // lintok-begin-on-ref
|
|
printStr(" $end\n");
|
|
|
|
makeNameMap();
|
|
|
|
// Signal header
|
|
assert(m_modDepth == 0);
|
|
printIndent(1);
|
|
printStr("\n");
|
|
|
|
// We detect the spaces in module names to determine hierarchy. This
|
|
// allows signals to be declared without fixed ordering, which is
|
|
// required as Verilog signals might be separately declared from
|
|
// SC module signals.
|
|
|
|
// Print the signal names
|
|
const char* lastName = "";
|
|
for (const auto& i : *m_namemapp) {
|
|
const std::string& hiernamestr = i.first;
|
|
const std::string& decl = i.second;
|
|
|
|
// Determine difference between the old and new names
|
|
const char* const hiername = hiernamestr.c_str();
|
|
const char* lp = lastName;
|
|
const char* np = hiername;
|
|
lastName = hiername;
|
|
|
|
// Skip common prefix, it must break at a space or tab
|
|
for (; *np && (*np == *lp); np++, lp++) {}
|
|
while (np != hiername && *np && *np != ' ' && *np != '\t') {
|
|
--np;
|
|
--lp;
|
|
}
|
|
// printf("hier %s\n lp=%s\n np=%s\n",hiername,lp,np);
|
|
|
|
// Any extra spaces in last name are scope ups we need to do
|
|
bool first = true;
|
|
for (; *lp; lp++) {
|
|
if (*lp == ' ' || (first && *lp != '\t')) {
|
|
printIndent(-1);
|
|
printStr("$upscope $end\n");
|
|
}
|
|
first = false;
|
|
}
|
|
|
|
// Any new spaces are scope downs we need to do
|
|
while (*np) {
|
|
if (*np == ' ') np++;
|
|
if (*np == '\t') break; // tab means signal name starts
|
|
printIndent(1);
|
|
// Find character after name end
|
|
const char* sp = np;
|
|
while (*sp && *sp != ' ' && *sp != '\t' && !(*sp & '\x80')) sp++;
|
|
|
|
printStr("$scope ");
|
|
if (*sp & '\x80') {
|
|
switch (*sp & 0x7f) {
|
|
case VLT_TRACE_SCOPE_STRUCT: printStr("struct "); break;
|
|
case VLT_TRACE_SCOPE_INTERFACE: printStr("interface "); break;
|
|
case VLT_TRACE_SCOPE_UNION: printStr("union "); break;
|
|
default: printStr("module ");
|
|
}
|
|
} else
|
|
printStr("module ");
|
|
|
|
for (; *np && *np != ' ' && *np != '\t'; np++) {
|
|
if (*np == '[') {
|
|
printStr("[");
|
|
} else if (*np == ']') {
|
|
printStr("]");
|
|
} else if (!(*np & '\x80')) {
|
|
*m_writep++ = *np;
|
|
}
|
|
}
|
|
printStr(" $end\n");
|
|
}
|
|
|
|
printIndent(0);
|
|
printStr(decl.c_str());
|
|
}
|
|
|
|
while (m_modDepth > 1) {
|
|
printIndent(-1);
|
|
printStr("$upscope $end\n");
|
|
}
|
|
|
|
printIndent(-1);
|
|
printStr("$enddefinitions $end\n\n\n");
|
|
assert(m_modDepth == 0);
|
|
|
|
// Reclaim storage
|
|
deleteNameMap();
|
|
}
|
|
|
|
void VerilatedVcd::declare(vluint32_t code, const char* name, const char* wirep, bool array,
|
|
int arraynum, bool tri, bool bussed, int msb, int lsb) {
|
|
const int bits = ((msb > lsb) ? (msb - lsb) : (lsb - msb)) + 1;
|
|
|
|
VerilatedTrace<VerilatedVcd>::declCode(code, bits, tri);
|
|
|
|
if (m_suffixes.size() <= nextCode() * VL_TRACE_SUFFIX_ENTRY_SIZE) {
|
|
m_suffixes.resize(nextCode() * VL_TRACE_SUFFIX_ENTRY_SIZE * 2, 0);
|
|
}
|
|
|
|
// Make sure write buffer is large enough (one character per bit), plus header
|
|
bufferResize(bits + 1024);
|
|
|
|
// Split name into basename
|
|
// Spaces and tabs aren't legal in VCD signal names, so:
|
|
// Space separates each level of scope
|
|
// Tab separates final scope from signal name
|
|
// Tab sorts before spaces, so signals nicely will print before scopes
|
|
// Note the hiername may be nothing, if so we'll add "\t{name}"
|
|
std::string nameasstr = namePrefix() + name;
|
|
std::string hiername;
|
|
std::string basename;
|
|
for (const char* cp = nameasstr.c_str(); *cp; cp++) {
|
|
if (isScopeEscape(*cp)) {
|
|
// Ahh, we've just read a scope, not a basename
|
|
if (!hiername.empty()) hiername += " ";
|
|
hiername += basename;
|
|
basename = "";
|
|
} else {
|
|
basename += *cp;
|
|
}
|
|
}
|
|
hiername += "\t" + basename;
|
|
|
|
// Print reference
|
|
std::string decl = "$var ";
|
|
if (m_evcd) {
|
|
decl += "port";
|
|
} else {
|
|
decl += wirep; // usually "wire"
|
|
}
|
|
|
|
constexpr size_t bufsize = 1000;
|
|
char buf[bufsize];
|
|
VL_SNPRINTF(buf, bufsize, " %2d ", bits);
|
|
decl += buf;
|
|
if (m_evcd) {
|
|
VL_SNPRINTF(buf, bufsize, "<%u", code);
|
|
decl += buf;
|
|
} else {
|
|
// Add string code to decl
|
|
char* const endp = writeCode(buf, code);
|
|
*endp = '\0';
|
|
decl += buf;
|
|
// Build suffix array entry
|
|
char* const entryp = &m_suffixes[code * VL_TRACE_SUFFIX_ENTRY_SIZE];
|
|
const size_t length = endp - buf;
|
|
assert(length <= VL_TRACE_MAX_VCD_CODE_SIZE);
|
|
// 1 bit values don't have a ' ' separator between value and string code
|
|
const bool isBit = bits == 1;
|
|
entryp[0] = ' '; // Separator
|
|
// Use memcpy as we checked size above, and strcpy is flagged unsafe
|
|
std::memcpy(entryp + !isBit, buf,
|
|
std::strlen(buf)); // Code (overwrite separator if isBit)
|
|
entryp[length + !isBit] = '\n'; // Replace '\0' with line termination '\n'
|
|
// Set length of suffix (used to increment write pointer)
|
|
entryp[VL_TRACE_SUFFIX_ENTRY_SIZE - 1] = !isBit + length + 1;
|
|
}
|
|
decl += " ";
|
|
decl += basename;
|
|
if (array) {
|
|
VL_SNPRINTF(buf, bufsize, "[%d]", arraynum);
|
|
decl += buf;
|
|
hiername += buf;
|
|
}
|
|
if (bussed) {
|
|
VL_SNPRINTF(buf, bufsize, " [%d:%d]", msb, lsb);
|
|
decl += buf;
|
|
}
|
|
decl += " $end\n";
|
|
m_namemapp->emplace(hiername, decl);
|
|
}
|
|
|
|
void VerilatedVcd::declBit(vluint32_t code, const char* name, bool array, int arraynum) {
|
|
declare(code, name, "wire", array, arraynum, false, false, 0, 0);
|
|
}
|
|
void VerilatedVcd::declBus(vluint32_t code, const char* name, bool array, int arraynum, int msb,
|
|
int lsb) {
|
|
declare(code, name, "wire", array, arraynum, false, true, msb, lsb);
|
|
}
|
|
void VerilatedVcd::declQuad(vluint32_t code, const char* name, bool array, int arraynum, int msb,
|
|
int lsb) {
|
|
declare(code, name, "wire", array, arraynum, false, true, msb, lsb);
|
|
}
|
|
void VerilatedVcd::declArray(vluint32_t code, const char* name, bool array, int arraynum, int msb,
|
|
int lsb) {
|
|
declare(code, name, "wire", array, arraynum, false, true, msb, lsb);
|
|
}
|
|
void VerilatedVcd::declDouble(vluint32_t code, const char* name, bool array, int arraynum) {
|
|
declare(code, name, "real", array, arraynum, false, false, 63, 0);
|
|
}
|
|
#ifdef VL_TRACE_VCD_OLD_API
|
|
void VerilatedVcd::declTriBit(vluint32_t code, const char* name, bool array, int arraynum) {
|
|
declare(code, name, "wire", array, arraynum, true, false, 0, 0);
|
|
}
|
|
void VerilatedVcd::declTriBus(vluint32_t code, const char* name, bool array, int arraynum, int msb,
|
|
int lsb) {
|
|
declare(code, name, "wire", array, arraynum, true, true, msb, lsb);
|
|
}
|
|
void VerilatedVcd::declTriQuad(vluint32_t code, const char* name, bool array, int arraynum,
|
|
int msb, int lsb) {
|
|
declare(code, name, "wire", array, arraynum, true, true, msb, lsb);
|
|
}
|
|
void VerilatedVcd::declTriArray(vluint32_t code, const char* name, bool array, int arraynum,
|
|
int msb, int lsb) {
|
|
declare(code, name, "wire", array, arraynum, true, true, msb, lsb);
|
|
}
|
|
#endif // VL_TRACE_VCD_OLD_API
|
|
|
|
//=============================================================================
|
|
// Trace rendering prinitives
|
|
|
|
static inline void
|
|
VerilatedVcdCCopyAndAppendNewLine(char* writep, const char* suffixp) VL_ATTR_NO_SANITIZE_ALIGN;
|
|
|
|
static inline 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<vluint64_t*>(writep) = *reinterpret_cast<const vluint64_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 VerilatedVcd::finishLine(vluint32_t code, char* writep) {
|
|
const char* const suffixp = m_suffixes.data() + code * VL_TRACE_SUFFIX_ENTRY_SIZE;
|
|
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];
|
|
bufferCheck();
|
|
}
|
|
|
|
//=============================================================================
|
|
// emit* trace routines
|
|
|
|
// Note: emit* are only ever called from one place (full* in
|
|
// verilated_trace_imp.cpp, which is included in this file at the top),
|
|
// so always inline them.
|
|
|
|
VL_ATTR_ALWINLINE
|
|
void VerilatedVcd::emitBit(vluint32_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 VerilatedVcd::emitCData(vluint32_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 VerilatedVcd::emitSData(vluint32_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 VerilatedVcd::emitIData(vluint32_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 VerilatedVcd::emitQData(vluint32_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 VerilatedVcd::emitWData(vluint32_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 VerilatedVcd::emitDouble(vluint32_t code, double newval) {
|
|
char* wp = m_writep;
|
|
// Buffer can't overflow before VL_SNPRINTF; we sized during declaration
|
|
VL_SNPRINTF(wp, m_wrChunkSize, "r%.16g", newval);
|
|
wp += std::strlen(wp);
|
|
finishLine(code, wp);
|
|
}
|
|
|
|
#ifdef VL_TRACE_VCD_OLD_API
|
|
|
|
void VerilatedVcd::fullBit(vluint32_t code, const vluint32_t newval) {
|
|
// Note the &1, so we don't require clean input -- makes more common no change case faster
|
|
*oldp(code) = newval;
|
|
*m_writep++ = ('0' + static_cast<char>(newval & 1));
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullBus(vluint32_t code, const vluint32_t newval, int bits) {
|
|
*oldp(code) = newval;
|
|
*m_writep++ = 'b';
|
|
for (int bit = bits - 1; bit >= 0; --bit) {
|
|
*m_writep++ = ((newval & (1L << bit)) ? '1' : '0');
|
|
}
|
|
*m_writep++ = ' ';
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullQuad(vluint32_t code, const vluint64_t newval, int bits) {
|
|
(*(reinterpret_cast<vluint64_t*>(oldp(code)))) = newval;
|
|
*m_writep++ = 'b';
|
|
for (int bit = bits - 1; bit >= 0; --bit) {
|
|
*m_writep++ = ((newval & (1ULL << bit)) ? '1' : '0');
|
|
}
|
|
*m_writep++ = ' ';
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullArray(vluint32_t code, const vluint32_t* newval, int bits) {
|
|
for (int word = 0; word < (((bits - 1) / 32) + 1); ++word) { oldp(code)[word] = newval[word]; }
|
|
*m_writep++ = 'b';
|
|
for (int bit = bits - 1; bit >= 0; --bit) {
|
|
*m_writep++ = ((newval[(bit / 32)] & (1L << (bit & 0x1f))) ? '1' : '0');
|
|
}
|
|
*m_writep++ = ' ';
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullArray(vluint32_t code, const vluint64_t* newval, int bits) {
|
|
for (int word = 0; word < (((bits - 1) / 64) + 1); ++word) { oldp(code)[word] = newval[word]; }
|
|
*m_writep++ = 'b';
|
|
for (int bit = bits - 1; bit >= 0; --bit) {
|
|
*m_writep++ = ((newval[(bit / 64)] & (1ULL << (bit & 0x3f))) ? '1' : '0');
|
|
}
|
|
*m_writep++ = ' ';
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullTriBit(vluint32_t code, const vluint32_t newval, const vluint32_t newtri) {
|
|
oldp(code)[0] = newval;
|
|
oldp(code)[1] = newtri;
|
|
*m_writep++ = "01zz"[newval | (newtri << 1)];
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullTriBus(vluint32_t code, const vluint32_t newval, const vluint32_t newtri,
|
|
int bits) {
|
|
oldp(code)[0] = newval;
|
|
oldp(code)[1] = newtri;
|
|
*m_writep++ = 'b';
|
|
for (int bit = bits - 1; bit >= 0; --bit) {
|
|
*m_writep++ = "01zz"[((newval >> bit) & 1) | (((newtri >> bit) & 1) << 1)];
|
|
}
|
|
*m_writep++ = ' ';
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullTriQuad(vluint32_t code, const vluint64_t newval, const vluint64_t newtri,
|
|
int bits) {
|
|
(*(reinterpret_cast<vluint64_t*>(oldp(code)))) = newval;
|
|
(*(reinterpret_cast<vluint64_t*>(oldp(code + 1)))) = newtri;
|
|
*m_writep++ = 'b';
|
|
for (int bit = bits - 1; bit >= 0; --bit) {
|
|
*m_writep++ = "01zz"[((newval >> bit) & 1ULL) | (((newtri >> bit) & 1ULL) << 1ULL)];
|
|
}
|
|
*m_writep++ = ' ';
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullTriArray(vluint32_t code, const vluint32_t* newvalp,
|
|
const vluint32_t* newtrip, int bits) {
|
|
for (int word = 0; word < (((bits - 1) / 32) + 1); ++word) {
|
|
oldp(code)[word * 2] = newvalp[word];
|
|
oldp(code)[word * 2 + 1] = newtrip[word];
|
|
}
|
|
*m_writep++ = 'b';
|
|
for (int bit = bits - 1; bit >= 0; --bit) {
|
|
vluint32_t valbit = (newvalp[(bit / 32)] >> (bit & 0x1f)) & 1;
|
|
vluint32_t tribit = (newtrip[(bit / 32)] >> (bit & 0x1f)) & 1;
|
|
*m_writep++ = "01zz"[valbit | (tribit << 1)];
|
|
}
|
|
*m_writep++ = ' ';
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullDouble(vluint32_t code, const double newval) {
|
|
// cppcheck-suppress invalidPointerCast
|
|
(*(reinterpret_cast<double*>(oldp(code)))) = newval;
|
|
// Buffer can't overflow before VL_SNPRINTF; we sized during declaration
|
|
VL_SNPRINTF(m_writep, m_wrChunkSize, "r%.16g", newval);
|
|
m_writep += std::strlen(m_writep);
|
|
*m_writep++ = ' ';
|
|
m_writep = writeCode(m_writep, code);
|
|
*m_writep++ = '\n';
|
|
bufferCheck();
|
|
}
|
|
|
|
#endif // VL_TRACE_VCD_OLD_API
|
|
|
|
//======================================================================
|
|
//======================================================================
|
|
//======================================================================
|
|
|
|
#ifdef VERILATED_VCD_TEST
|
|
#include <iostream>
|
|
|
|
extern void verilated_trace_imp_selftest();
|
|
|
|
vluint32_t v1, v2, s1, s2[3];
|
|
vluint32_t tri96[3];
|
|
vluint32_t tri96__tri[3];
|
|
vluint64_t quad96[2];
|
|
vluint64_t tquad;
|
|
vluint64_t tquad__tri;
|
|
vluint8_t ch;
|
|
vluint64_t timestamp = 1;
|
|
double doub = 0.0;
|
|
float flo = 0.0f;
|
|
|
|
void vcdInit(void*, VerilatedVcd* vcdp, vluint32_t) {
|
|
vcdp->scopeEscape('.');
|
|
vcdp->pushNamePrefix("top.");
|
|
/**/ vcdp->declBus(0x2, "v1", -1, 0, 5, 1);
|
|
/**/ vcdp->declBus(0x3, "v2", -1, 0, 6, 1);
|
|
/**/ vcdp->pushNamePrefix("sub1.");
|
|
/***/ vcdp->declBit(0x4, "s1", -1, 0);
|
|
/***/ vcdp->declBit(0x5, "ch", -1, 0);
|
|
/**/ vcdp->popNamePrefix();
|
|
/**/ vcdp->pushNamePrefix("sub2.");
|
|
/***/ vcdp->declArray(0x6, "s2", -1, 0, 40, 3);
|
|
/**/ vcdp->popNamePrefix();
|
|
vcdp->popNamePrefix();
|
|
// Note need to add 3 for next code.
|
|
vcdp->pushNamePrefix("top2.");
|
|
/**/ vcdp->declBus(0x2, "t2v1", -1, 0, 4, 1);
|
|
/**/ vcdp->declTriBit(0x10, "io1", -1, 0);
|
|
/**/ vcdp->declTriBus(0x12, "io5", -1, 0, 4, 0);
|
|
/**/ vcdp->declTriArray(0x16, "io96", -1, 0, 95, 0);
|
|
/**/ // Note need to add 6 for next code.
|
|
/**/ vcdp->declDouble(0x1c, "doub", -1, 0);
|
|
/**/ // Note need to add 2 for next code.
|
|
/**/ vcdp->declArray(0x20, "q2", -1, 0, 95, 0);
|
|
/**/ // Note need to add 4 for next code.
|
|
/**/ vcdp->declTriQuad(0x24, "tq", -1, 0, 63, 0);
|
|
/**/ // Note need to add 4 for next code.
|
|
vcdp->popNamePrefix();
|
|
}
|
|
|
|
void vcdFull(void*, VerilatedVcd* vcdp) {
|
|
vcdp->fullBus(0x2, v1, 5);
|
|
vcdp->fullBus(0x3, v2, 7);
|
|
vcdp->fullBit(0x4, s1);
|
|
vcdp->fullBus(0x5, ch, 2);
|
|
vcdp->fullArray(0x6, &s2[0], 38);
|
|
vcdp->fullTriBit(0x10, tri96[0] & 1, tri96__tri[0] & 1);
|
|
vcdp->fullTriBus(0x12, tri96[0] & 0x1f, tri96__tri[0] & 0x1f, 5);
|
|
vcdp->fullTriArray(0x16, tri96, tri96__tri, 96);
|
|
vcdp->fullDouble(0x1c, doub);
|
|
vcdp->fullArray(0x20, &quad96[0], 96);
|
|
vcdp->fullTriQuad(0x24, tquad, tquad__tri, 64);
|
|
}
|
|
|
|
void vcdChange(void*, VerilatedVcd* vcdp) {
|
|
vcdp->chgBus(0x2, v1, 5);
|
|
vcdp->chgBus(0x3, v2, 7);
|
|
vcdp->chgBit(0x4, s1);
|
|
vcdp->chgBus(0x5, ch, 2);
|
|
vcdp->chgArray(0x6, &s2[0], 38);
|
|
vcdp->chgTriBit(0x10, tri96[0] & 1, tri96__tri[0] & 1);
|
|
vcdp->chgTriBus(0x12, tri96[0] & 0x1f, tri96__tri[0] & 0x1f, 5);
|
|
vcdp->chgTriArray(0x16, tri96, tri96__tri, 96);
|
|
vcdp->chgDouble(0x1c, doub);
|
|
vcdp->chgArray(0x20, &quad96[0], 96);
|
|
vcdp->chgTriQuad(0x24, tquad, tquad__tri, 64);
|
|
}
|
|
|
|
// clang-format off
|
|
void vcdTestMain(const char* filenamep) {
|
|
verilated_trace_imp_selftest();
|
|
|
|
v1 = v2 = s1 = 0;
|
|
s2[0] = s2[1] = s2[2] = 0;
|
|
tri96[2] = tri96[1] = tri96[0] = 0;
|
|
tri96__tri[2] = tri96__tri[1] = tri96__tri[0] = ~0;
|
|
quad96[1] = quad96[0] = 0;
|
|
ch = 0;
|
|
doub = 0;
|
|
tquad = tquad__tri = 0;
|
|
{
|
|
VerilatedVcdC* vcdp = new VerilatedVcdC;
|
|
vcdp->evcd(true);
|
|
vcdp->set_time_unit("1ms");
|
|
vcdp->set_time_unit(std::string{"1ms"});
|
|
vcdp->set_time_resolution("1ns");
|
|
vcdp->set_time_resolution(std::string{"1ns"});
|
|
vcdp->spTrace()->addInitCb(&vcdInit, 0);
|
|
vcdp->spTrace()->addFullCb(&vcdFull, 0);
|
|
vcdp->spTrace()->addChgCb(&vcdChange, 0);
|
|
vcdp->open(filenamep);
|
|
// Dumping
|
|
vcdp->dump(++timestamp);
|
|
v1 = 0xfff;
|
|
tri96[2] = 4; tri96[1] = 2; tri96[0] = 1;
|
|
tri96__tri[2] = tri96__tri[1] = tri96__tri[0] = ~0; // Still tri
|
|
quad96[1] = 0xffffffff; quad96[0] = 0;
|
|
doub = 1.5;
|
|
flo = 1.4f;
|
|
vcdp->dump(++timestamp);
|
|
v2 = 0x1;
|
|
s2[1] = 2;
|
|
tri96__tri[2] = tri96__tri[1] = tri96__tri[0] = 0; // enable w/o data change
|
|
quad96[1] = 0; quad96[0] = ~0;
|
|
doub = -1.66e13;
|
|
flo = 0.123f;
|
|
tquad = 0x00ff00ff00ff00ffULL;
|
|
tquad__tri = 0x0000fffff0000ffffULL;
|
|
vcdp->dump(++timestamp);
|
|
ch = 2;
|
|
tri96[2] = ~4; tri96[1] = ~2; tri96[0] = ~1;
|
|
doub = -3.33e-13;
|
|
vcdp->dump(++timestamp);
|
|
vcdp->dump(++timestamp);
|
|
# ifdef VERILATED_VCD_TEST_64BIT
|
|
const vluint64_t bytesPerDump = 15ULL;
|
|
for (vluint64_t i = 0; i < ((1ULL << 32) / bytesPerDump); i++) {
|
|
v1 = i;
|
|
vcdp->dump(++timestamp);
|
|
}
|
|
# endif
|
|
vcdp->close();
|
|
VL_DO_CLEAR(delete vcdp, vcdp = nullptr);
|
|
}
|
|
}
|
|
#endif
|
|
// clang-format on
|
|
|
|
//********************************************************************
|
|
// ;compile-command: "v4make test_regress/t/t_trace_c_api.pl"
|
|
//
|
|
// Local Variables:
|
|
// End:
|