mirror of
https://github.com/verilator/verilator.git
synced 2025-01-01 04:07:34 +00:00
809 lines
28 KiB
C++
809 lines
28 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//=============================================================================
|
|
//
|
|
// THIS MODULE IS PUBLICLY LICENSED
|
|
//
|
|
// Copyright 2001-2019 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.
|
|
//
|
|
// This is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
// for more details.
|
|
//
|
|
//=============================================================================
|
|
///
|
|
/// \file
|
|
/// \brief C++ Tracing in VCD Format
|
|
///
|
|
//=============================================================================
|
|
// SPDIFF_OFF
|
|
|
|
#include "verilatedos.h"
|
|
#include "verilated.h"
|
|
#include "verilated_vcd_c.h"
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <ctime>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
|
|
#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
|
|
# include <io.h>
|
|
#else
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
// SPDIFF_ON
|
|
|
|
#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
|
|
|
|
//=============================================================================
|
|
// VerilatedVcdImp
|
|
/// Base class to hold some static state
|
|
/// This is an internally used class
|
|
|
|
class VerilatedVcdSingleton {
|
|
private:
|
|
typedef std::vector<VerilatedVcd*> VcdVec;
|
|
struct Singleton {
|
|
VerilatedMutex s_vcdMutex; ///< Protect the singleton
|
|
VcdVec s_vcdVecp VL_GUARDED_BY(s_vcdMutex); ///< List of all created traces
|
|
};
|
|
static Singleton& singleton() { static Singleton s; return s; }
|
|
public:
|
|
static void pushVcd(VerilatedVcd* vcdp) VL_EXCLUDES(singleton().s_vcdMutex) {
|
|
VerilatedLockGuard lock(singleton().s_vcdMutex);
|
|
singleton().s_vcdVecp.push_back(vcdp);
|
|
}
|
|
static void removeVcd(const VerilatedVcd* vcdp) VL_EXCLUDES(singleton().s_vcdMutex) {
|
|
VerilatedLockGuard lock(singleton().s_vcdMutex);
|
|
VcdVec::iterator pos = find(singleton().s_vcdVecp.begin(),
|
|
singleton().s_vcdVecp.end(), vcdp);
|
|
if (pos != singleton().s_vcdVecp.end()) { singleton().s_vcdVecp.erase(pos); }
|
|
}
|
|
static void flush_all() VL_EXCLUDES(singleton().s_vcdMutex) VL_MT_UNSAFE_ONE {
|
|
// Thread safety: Although this function is protected by a mutex so
|
|
// perhaps in the future we can allow tracing in separate threads,
|
|
// vcdp->flush() assumes call from single thread
|
|
VerilatedLockGuard lock(singleton().s_vcdMutex);
|
|
for (VcdVec::const_iterator it = singleton().s_vcdVecp.begin();
|
|
it != singleton().s_vcdVecp.end(); ++it) {
|
|
VerilatedVcd* vcdp = *it;
|
|
vcdp->flush();
|
|
}
|
|
}
|
|
};
|
|
|
|
//=============================================================================
|
|
// VerilatedVcdCallInfo
|
|
/// Internal callback routines for each module being traced.
|
|
////
|
|
/// Each module that wishes to be traced registers a set of
|
|
/// callbacks stored in this class. When the trace file is being
|
|
/// constructed, this class provides the callback routines to be executed.
|
|
|
|
class VerilatedVcdCallInfo {
|
|
protected:
|
|
friend class VerilatedVcd;
|
|
VerilatedVcdCallback_t m_initcb; ///< Initialization Callback function
|
|
VerilatedVcdCallback_t m_fullcb; ///< Full Dumping Callback function
|
|
VerilatedVcdCallback_t m_changecb; ///< Incremental Dumping Callback function
|
|
void* m_userthis; ///< Fake "this" for caller
|
|
vluint32_t m_code; ///< Starting code number
|
|
// CONSTRUCTORS
|
|
VerilatedVcdCallInfo(VerilatedVcdCallback_t icb, VerilatedVcdCallback_t fcb,
|
|
VerilatedVcdCallback_t changecb,
|
|
void* ut, vluint32_t code)
|
|
: m_initcb(icb), m_fullcb(fcb), m_changecb(changecb), m_userthis(ut), m_code(code) {}
|
|
~VerilatedVcdCallInfo() {}
|
|
};
|
|
|
|
//=============================================================================
|
|
//=============================================================================
|
|
//=============================================================================
|
|
// 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)
|
|
: m_isOpen(false), m_rolloverMB(0), m_modDepth(0), m_nextCode(1) {
|
|
// Not in header to avoid link issue if header is included without this .cpp file
|
|
m_fileNewed = (filep == NULL);
|
|
m_filep = m_fileNewed ? new VerilatedVcdFile : filep;
|
|
m_namemapp = NULL;
|
|
m_timeRes = m_timeUnit = 1e-9;
|
|
m_timeLastDump = 0;
|
|
m_sigs_oldvalp = NULL;
|
|
m_evcd = false;
|
|
m_scopeEscape = '.'; // Backward compatibility
|
|
m_fullDump = true;
|
|
m_wrChunkSize = 8*1024;
|
|
m_wrBufp = new char [m_wrChunkSize*8];
|
|
m_wrFlushp = m_wrBufp + m_wrChunkSize * 6;
|
|
m_writep = m_wrBufp;
|
|
m_wroteBytes = 0;
|
|
}
|
|
|
|
void VerilatedVcd::open(const char* filename) {
|
|
m_assertOne.check();
|
|
if (isOpen()) return;
|
|
|
|
// Set member variables
|
|
m_filename = filename;
|
|
VerilatedVcdSingleton::pushVcd(this);
|
|
|
|
// SPDIFF_OFF
|
|
// Set callback so an early exit will flush us
|
|
Verilated::flushCb(&flush_all);
|
|
|
|
// SPDIFF_ON
|
|
openNext(m_rolloverMB!=0);
|
|
if (!isOpen()) return;
|
|
|
|
dumpHeader();
|
|
|
|
// Allocate space now we know the number of codes
|
|
if (!m_sigs_oldvalp) {
|
|
m_sigs_oldvalp = new vluint32_t [m_nextCode+10];
|
|
}
|
|
|
|
if (m_rolloverMB) {
|
|
openNext(true);
|
|
if (!isOpen()) return;
|
|
}
|
|
}
|
|
|
|
void VerilatedVcd::openNext(bool incFilename) {
|
|
// Open next filename in concat sequence, mangle filename if
|
|
// incFilename is true.
|
|
m_assertOne.check();
|
|
closePrev(); // Close existing
|
|
if (incFilename) {
|
|
// Find _0000.{ext} in filename
|
|
std::string name = m_filename;
|
|
size_t pos = name.rfind('.');
|
|
if (pos>8 && 0==strncmp("_cat",name.c_str()+pos-8,4)
|
|
&& isdigit(name.c_str()[pos-4])
|
|
&& isdigit(name.c_str()[pos-3])
|
|
&& isdigit(name.c_str()[pos-2])
|
|
&& 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 (m_filename[0]=='|') {
|
|
assert(0); // 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;
|
|
m_fullDump = true; // First dump must be full
|
|
m_wroteBytes = 0;
|
|
}
|
|
|
|
void VerilatedVcd::makeNameMap() {
|
|
// Take signal information from each module and build m_namemapp
|
|
deleteNameMap();
|
|
m_nextCode = 1;
|
|
m_namemapp = new NameMap;
|
|
for (vluint32_t ent = 0; ent< m_callbacks.size(); ent++) {
|
|
VerilatedVcdCallInfo* cip = m_callbacks[ent];
|
|
cip->m_code = m_nextCode;
|
|
(cip->m_initcb)(this, cip->m_userthis, cip->m_code);
|
|
}
|
|
|
|
// 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 (NameMap::const_iterator it=m_namemapp->begin(); it!=m_namemapp->end(); ++it) {
|
|
const std::string& hiername = it->first;
|
|
if (!hiername.empty() && hiername[0] == '\t') nullScope=true;
|
|
}
|
|
if (nullScope) {
|
|
NameMap* newmapp = new NameMap;
|
|
for (NameMap::const_iterator it=m_namemapp->begin(); it!=m_namemapp->end(); ++it) {
|
|
const std::string& hiername = it->first;
|
|
const std::string& decl = it->second;
|
|
std::string newname = std::string("top");
|
|
if (hiername[0] != '\t') newname += ' ';
|
|
newname += hiername;
|
|
newmapp->insert(std::make_pair(newname,decl));
|
|
}
|
|
deleteNameMap();
|
|
m_namemapp = newmapp;
|
|
}
|
|
}
|
|
|
|
void VerilatedVcd::deleteNameMap() {
|
|
if (m_namemapp) { delete m_namemapp; m_namemapp=NULL; }
|
|
}
|
|
|
|
VerilatedVcd::~VerilatedVcd() {
|
|
close();
|
|
if (m_wrBufp) { delete[] m_wrBufp; m_wrBufp=NULL; }
|
|
if (m_sigs_oldvalp) { delete[] m_sigs_oldvalp; m_sigs_oldvalp=NULL; }
|
|
deleteNameMap();
|
|
if (m_filep && m_fileNewed) { delete m_filep; m_filep = NULL; }
|
|
for (CallbackVec::const_iterator it=m_callbacks.begin(); it!=m_callbacks.end(); ++it) {
|
|
delete (*it);
|
|
}
|
|
m_callbacks.clear();
|
|
VerilatedVcdSingleton::removeVcd(this);
|
|
}
|
|
|
|
void VerilatedVcd::closePrev() {
|
|
// This function is on the flush() call path
|
|
if (!isOpen()) return;
|
|
|
|
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() {
|
|
// This function is on the flush() call path
|
|
m_assertOne.check();
|
|
if (!isOpen()) return;
|
|
if (m_evcd) {
|
|
printStr("$vcdclose ");
|
|
printTime(m_timeLastDump);
|
|
printStr(" $end\n");
|
|
}
|
|
closePrev();
|
|
}
|
|
|
|
void VerilatedVcd::printStr(const char* str) {
|
|
// Not fast...
|
|
while (*str) {
|
|
*m_writep++ = *str++;
|
|
bufferCheck();
|
|
}
|
|
}
|
|
|
|
void VerilatedVcd::printQuad(vluint64_t n) {
|
|
char buf [100];
|
|
sprintf(buf,"%" VL_PRI64 "u", n);
|
|
printStr(buf);
|
|
}
|
|
|
|
void VerilatedVcd::printTime(vluint64_t timeui) {
|
|
// VCD file format specification does not allow non-integers for timestamps
|
|
// Dinotrace doesn't mind, but Cadence vvision seems to choke
|
|
if (VL_UNLIKELY(timeui < m_timeLastDump)) {
|
|
timeui = m_timeLastDump;
|
|
static VL_THREAD_LOCAL bool backTime = false;
|
|
if (!backTime) {
|
|
backTime = true;
|
|
VL_PRINTF_MT("%%Warning: VCD time is moving backwards, wave file may be incorrect.\n");
|
|
}
|
|
}
|
|
m_timeLastDump = timeui;
|
|
printQuad(timeui);
|
|
}
|
|
|
|
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)) {
|
|
char* oldbufp = m_wrBufp;
|
|
m_wrChunkSize = minsize*2;
|
|
m_wrBufp = new char [m_wrChunkSize * 8];
|
|
memcpy(m_wrBufp, oldbufp, m_writep - oldbufp);
|
|
m_writep = m_wrBufp + (m_writep - oldbufp);
|
|
m_wrFlushp = m_wrBufp + m_wrChunkSize * 6;
|
|
delete [] oldbufp; oldbufp=NULL;
|
|
}
|
|
}
|
|
|
|
void VerilatedVcd::bufferFlush() VL_MT_UNSAFE_ONE {
|
|
// 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
|
|
m_assertOne.check();
|
|
if (VL_UNLIKELY(!isOpen())) return;
|
|
char* wp = m_wrBufp;
|
|
while (1) {
|
|
ssize_t remaining = (m_writep - wp);
|
|
if (remaining==0) break;
|
|
errno = 0;
|
|
ssize_t got = m_filep->write(wp, remaining);
|
|
if (got>0) {
|
|
wp += got;
|
|
m_wroteBytes += got;
|
|
} else if (got < 0) {
|
|
if (errno != EAGAIN && errno != EINTR) {
|
|
// write failed, presume error (perhaps out of disk space)
|
|
std::string msg = std::string("VerilatedVcd::bufferFlush: ")+strerror(errno);
|
|
VL_FATAL_MT("",0,"",msg.c_str());
|
|
closeErr();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset buffer
|
|
m_writep = m_wrBufp;
|
|
}
|
|
|
|
//=============================================================================
|
|
// Simple methods
|
|
|
|
void VerilatedVcd::set_time_unit(const char* unitp) {
|
|
//cout<<" set_time_unit("<<unitp<<") == "<<timescaleToDouble(unitp)
|
|
// <<" == "<<doubleToTimescale(timescaleToDouble(unitp))<<endl;
|
|
m_timeUnit = timescaleToDouble(unitp);
|
|
}
|
|
|
|
void VerilatedVcd::set_time_resolution(const char* unitp) {
|
|
//cout<<"set_time_resolution("<<unitp<<") == "<<timescaleToDouble(unitp)
|
|
// <<" == "<<doubleToTimescale(timescaleToDouble(unitp))<<endl;
|
|
m_timeRes = timescaleToDouble(unitp);
|
|
}
|
|
|
|
double VerilatedVcd::timescaleToDouble(const char* unitp) {
|
|
char* endp;
|
|
double value = strtod(unitp, &endp);
|
|
if (value==0.0 && endp==unitp) value=1; // On error so we allow just "ns" to return 1e-9.
|
|
unitp=endp;
|
|
while (*unitp && isspace(*unitp)) unitp++;
|
|
switch (*unitp) {
|
|
case 's': value *= 1e1; break;
|
|
case 'm': value *= 1e-3; break;
|
|
case 'u': value *= 1e-6; break;
|
|
case 'n': value *= 1e-9; break;
|
|
case 'p': value *= 1e-12; break;
|
|
case 'f': value *= 1e-15; break;
|
|
case 'a': value *= 1e-18; break;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
std::string VerilatedVcd::doubleToTimescale(double value) {
|
|
const char* suffixp = "s";
|
|
if (value>=1e0) { suffixp="s"; value *= 1e0; }
|
|
else if (value>=1e-3 ) { suffixp="ms"; value *= 1e3; }
|
|
else if (value>=1e-6 ) { suffixp="us"; value *= 1e6; }
|
|
else if (value>=1e-9 ) { suffixp="ns"; value *= 1e9; }
|
|
else if (value>=1e-12) { suffixp="ps"; value *= 1e12; }
|
|
else if (value>=1e-15) { suffixp="fs"; value *= 1e15; }
|
|
else if (value>=1e-18) { suffixp="as"; value *= 1e18; }
|
|
char valuestr[100]; sprintf(valuestr,"%3.0f%s", value, suffixp);
|
|
return valuestr; // Gets converted to string, so no ref to stack
|
|
}
|
|
|
|
//=============================================================================
|
|
// 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");
|
|
time_t time_str = time(NULL);
|
|
printStr("$date "); printStr(ctime(&time_str)); printStr(" $end\n");
|
|
|
|
printStr("$timescale ");
|
|
const std::string& timeResStr = doubleToTimescale(m_timeRes);
|
|
printStr(timeResStr.c_str());
|
|
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 (NameMap::const_iterator it=m_namemapp->begin(); it!=m_namemapp->end(); ++it) {
|
|
const std::string& hiernamestr = it->first;
|
|
const std::string& decl = it->second;
|
|
|
|
// Determine difference between the old and new names
|
|
const char* 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);
|
|
printStr("$scope module ");
|
|
for (; *np && *np!=' ' && *np!='\t'; np++) {
|
|
if (*np=='[') printStr("(");
|
|
else if (*np==']') printStr(")");
|
|
else *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::module(const std::string& name) {
|
|
m_assertOne.check();
|
|
m_modName = name;
|
|
}
|
|
|
|
void VerilatedVcd::declare(vluint32_t code, const char* name, const char* wirep,
|
|
int arraynum, bool tri, bool bussed, int msb, int lsb) {
|
|
if (!code) { VL_FATAL_MT(__FILE__, __LINE__, "",
|
|
"Internal: internal trace problem, code 0 is illegal"); }
|
|
|
|
int bits = ((msb>lsb)?(msb-lsb):(lsb-msb))+1;
|
|
int codesNeeded = 1+int(bits/32);
|
|
if (tri) codesNeeded *= 2; // Space in change array for __en signals
|
|
|
|
// Make sure array is large enough
|
|
m_nextCode = std::max(m_nextCode, code+codesNeeded);
|
|
if (m_sigs.capacity() <= m_nextCode) {
|
|
m_sigs.reserve(m_nextCode*2); // Power-of-2 allocation speeds things up
|
|
}
|
|
|
|
// Make sure write buffer is large enough (one character per bit), plus header
|
|
bufferResize(bits+1024);
|
|
|
|
// Save declaration info
|
|
VerilatedVcdSig sig = VerilatedVcdSig(code, bits);
|
|
m_sigs.push_back(sig);
|
|
|
|
// 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 = name;
|
|
if (!m_modName.empty()) {
|
|
nameasstr = m_modName+m_scopeEscape+nameasstr; // Optional ->module prefix
|
|
}
|
|
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"
|
|
char buf [1000];
|
|
sprintf(buf, " %2d ", bits);
|
|
decl += buf;
|
|
if (m_evcd) {
|
|
sprintf(buf, "<%u", code);
|
|
decl += buf;
|
|
} else {
|
|
decl += stringCode(code);
|
|
}
|
|
decl += " ";
|
|
decl += basename;
|
|
if (arraynum>=0) {
|
|
sprintf(buf, "(%d)", arraynum);
|
|
decl += buf;
|
|
hiername += buf;
|
|
}
|
|
if (bussed) {
|
|
sprintf(buf, " [%d:%d]", msb, lsb);
|
|
decl += buf;
|
|
}
|
|
decl += " $end\n";
|
|
m_namemapp->insert(std::make_pair(hiername,decl));
|
|
}
|
|
|
|
void VerilatedVcd::declBit (vluint32_t code, const char* name, int arraynum)
|
|
{ declare(code, name, "wire", arraynum, false, false, 0, 0); }
|
|
void VerilatedVcd::declBus (vluint32_t code, const char* name, int arraynum, int msb, int lsb)
|
|
{ declare(code, name, "wire", arraynum, false, true, msb, lsb); }
|
|
void VerilatedVcd::declQuad (vluint32_t code, const char* name, int arraynum, int msb, int lsb)
|
|
{ declare(code, name, "wire", arraynum, false, true, msb, lsb); }
|
|
void VerilatedVcd::declArray (vluint32_t code, const char* name, int arraynum, int msb, int lsb)
|
|
{ declare(code, name, "wire", arraynum, false, true, msb, lsb); }
|
|
void VerilatedVcd::declTriBit (vluint32_t code, const char* name, int arraynum)
|
|
{ declare(code, name, "wire", arraynum, true, false, 0, 0); }
|
|
void VerilatedVcd::declTriBus (vluint32_t code, const char* name, int arraynum, int msb, int lsb)
|
|
{ declare(code, name, "wire", arraynum, true, true, msb, lsb); }
|
|
void VerilatedVcd::declTriQuad (vluint32_t code, const char* name, int arraynum, int msb, int lsb)
|
|
{ declare(code, name, "wire", arraynum, true, true, msb, lsb); }
|
|
void VerilatedVcd::declTriArray (vluint32_t code, const char* name, int arraynum, int msb, int lsb)
|
|
{ declare(code, name, "wire", arraynum, true, true, msb, lsb); }
|
|
void VerilatedVcd::declFloat (vluint32_t code, const char* name, int arraynum)
|
|
{ declare(code, name, "real", arraynum, false, false, 31, 0); }
|
|
void VerilatedVcd::declDouble (vluint32_t code, const char* name, int arraynum)
|
|
{ declare(code, name, "real", arraynum, false, false, 63, 0); }
|
|
|
|
//=============================================================================
|
|
|
|
void VerilatedVcd::fullDouble(vluint32_t code, const double newval) {
|
|
// cppcheck-suppress invalidPointerCast
|
|
(*(reinterpret_cast<double*>(&m_sigs_oldvalp[code]))) = newval;
|
|
// Buffer can't overflow before sprintf; we sized during declaration
|
|
sprintf(m_writep, "r%.16g", newval);
|
|
m_writep += strlen(m_writep);
|
|
*m_writep++=' '; printCode(code); *m_writep++='\n';
|
|
bufferCheck();
|
|
}
|
|
void VerilatedVcd::fullFloat(vluint32_t code, const float newval) {
|
|
// cppcheck-suppress invalidPointerCast
|
|
(*(reinterpret_cast<float*>(&m_sigs_oldvalp[code]))) = newval;
|
|
// Buffer can't overflow before sprintf; we sized during declaration
|
|
sprintf(m_writep, "r%.16g", static_cast<double>(newval));
|
|
m_writep += strlen(m_writep);
|
|
*m_writep++=' '; printCode(code); *m_writep++='\n';
|
|
bufferCheck();
|
|
}
|
|
|
|
//=============================================================================
|
|
// Callbacks
|
|
|
|
void VerilatedVcd::addCallback(
|
|
VerilatedVcdCallback_t initcb, VerilatedVcdCallback_t fullcb, VerilatedVcdCallback_t changecb,
|
|
void* userthis) VL_MT_UNSAFE_ONE
|
|
{
|
|
m_assertOne.check();
|
|
if (VL_UNLIKELY(isOpen())) {
|
|
std::string msg = std::string("Internal: ")+__FILE__+"::"+__FUNCTION__
|
|
+" called with already open file";
|
|
VL_FATAL_MT(__FILE__, __LINE__, "", msg.c_str());
|
|
}
|
|
VerilatedVcdCallInfo* vci
|
|
= new VerilatedVcdCallInfo(initcb, fullcb, changecb, userthis, m_nextCode);
|
|
m_callbacks.push_back(vci);
|
|
}
|
|
|
|
//=============================================================================
|
|
// Dumping
|
|
|
|
void VerilatedVcd::dumpFull(vluint64_t timeui) {
|
|
m_assertOne.check();
|
|
dumpPrep(timeui);
|
|
Verilated::quiesce();
|
|
for (vluint32_t ent = 0; ent< m_callbacks.size(); ent++) {
|
|
VerilatedVcdCallInfo* cip = m_callbacks[ent];
|
|
(cip->m_fullcb)(this, cip->m_userthis, cip->m_code);
|
|
}
|
|
}
|
|
|
|
void VerilatedVcd::dump(vluint64_t timeui) {
|
|
m_assertOne.check();
|
|
if (!isOpen()) return;
|
|
if (VL_UNLIKELY(m_fullDump)) {
|
|
m_fullDump = false; // No need for more full dumps
|
|
dumpFull(timeui);
|
|
return;
|
|
}
|
|
if (VL_UNLIKELY(m_rolloverMB && m_wroteBytes > this->m_rolloverMB)) {
|
|
openNext(true);
|
|
if (!isOpen()) return;
|
|
}
|
|
dumpPrep(timeui);
|
|
Verilated::quiesce();
|
|
for (vluint32_t ent = 0; ent< m_callbacks.size(); ++ent) {
|
|
VerilatedVcdCallInfo* cip = m_callbacks[ent];
|
|
(cip->m_changecb)(this, cip->m_userthis, cip->m_code);
|
|
}
|
|
}
|
|
|
|
void VerilatedVcd::dumpPrep(vluint64_t timeui) {
|
|
printStr("#");
|
|
printTime(timeui);
|
|
printStr("\n");
|
|
}
|
|
|
|
//======================================================================
|
|
// Static members
|
|
|
|
void VerilatedVcd::flush_all() VL_MT_UNSAFE_ONE {
|
|
VerilatedVcdSingleton::flush_all();
|
|
}
|
|
|
|
//======================================================================
|
|
//======================================================================
|
|
//======================================================================
|
|
|
|
#ifdef VERILATED_VCD_TEST
|
|
vluint32_t v1, v2, s1, s2[3];
|
|
vluint32_t tri96[3];
|
|
vluint32_t tri96__tri[3];
|
|
vluint8_t ch;
|
|
vluint64_t timestamp = 1;
|
|
double doub = 0;
|
|
|
|
void vcdInit(VerilatedVcd* vcdp, void* userthis, vluint32_t code) {
|
|
vcdp->scopeEscape('.');
|
|
vcdp->module("top");
|
|
vcdp->declBus(0x2, "v1",-1,5,1);
|
|
vcdp->declBus(0x3, "v2",-1,6,0);
|
|
vcdp->module("top.sub1");
|
|
vcdp->declBit(0x4, "s1",-1);
|
|
vcdp->declBit(0x5, "ch",-1);
|
|
vcdp->module("top.sub2");
|
|
vcdp->declArray(0x6, "s2",-1, 40,3);
|
|
// Note need to add 3 for next code.
|
|
vcdp->module("top2");
|
|
vcdp->declBus(0x2, "t2v1",-1,4,1);
|
|
vcdp->declTriBit (0x10, "io1", -1);
|
|
vcdp->declTriBus (0x12, "io5", -1,4,0);
|
|
vcdp->declTriArray(0x16, "io96",-1,95,0);
|
|
// Note need to add 6 for next code.
|
|
vcdp->declDouble (0x1c, "doub",-1);
|
|
// Note need to add 2 for next code.
|
|
}
|
|
|
|
void vcdFull(VerilatedVcd* vcdp, void* userthis, vluint32_t code) {
|
|
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);
|
|
}
|
|
|
|
void vcdChange(VerilatedVcd* vcdp, void* userthis, vluint32_t code) {
|
|
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);
|
|
}
|
|
|
|
main() {
|
|
cout<<"test: O_LARGEFILE="<<O_LARGEFILE<<endl;
|
|
|
|
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;
|
|
ch = 0;
|
|
doub = 0;
|
|
{
|
|
VerilatedVcdC* vcdp = new VerilatedVcdC;
|
|
vcdp->spTrace()->addCallback(&vcdInit, &vcdFull, &vcdChange, 0);
|
|
vcdp->open("test.vcd");
|
|
// 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
|
|
doub = 1.5;
|
|
vcdp->dump(timestamp++);
|
|
v2 = 0x1;
|
|
s2[1] = 2;
|
|
tri96__tri[2] = tri96__tri[1] = tri96__tri[0] = 0; // enable w/o data change
|
|
doub = -1.66e13;
|
|
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
|
|
vluint64_t bytesPerDump = 15ULL;
|
|
for (vluint64_t i=0; i<((1ULL<<32) / bytesPerDump); i++) {
|
|
v1 = i;
|
|
vcdp->dump(timestamp++);
|
|
}
|
|
# endif
|
|
vcdp->close();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//********************************************************************
|
|
// Local Variables:
|
|
// compile-command: "mkdir -p ../test_dir && cd ../test_dir && c++ -DVERILATED_VCD_TEST ../src/verilated_vcd_c.cpp -o verilated_vcd_c && ./verilated_vcd_c && cat test.vcd"
|
|
// End:
|