// $Revision: #21 $$Date: 2003/07/16 $$Author: wsnyder $ -*- SystemC -*-
//=============================================================================
//
// THIS MODULE IS PUBLICLY LICENSED
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of either the GNU General Public License or the
// Perl Artistic License.
//
// 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.
//
// You should have received a copy of the GNU General Public License
// along with this module; see the file COPYING.  If not, write to
// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.
//
//=============================================================================
//
// AUTHOR:  Wilson Snyder
//
// DESCRIPTION: Tracing in vcd Format
//
//=============================================================================

#include <time.h>
#include <iostream>
#include <fstream>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

#include "SpTraceVcd.h"

//=============================================================================
// Global

vector<SpTraceVcd*>	SpTraceVcd::s_vcdVecp;	// List of all created traces

//=============================================================================
// SpTraceCallback

class SpTraceCallInfo {
protected:
    friend class SpTraceVcd;
    SpTraceCallback_t	m_initcb;	// Initalization Callback function
    SpTraceCallback_t	m_dumpcb;	// Dumping Callback function
    void*		m_userthis;	// Fake "this" for caller
    uint32_t		m_code;		// Starting code number
    SpTraceCallInfo (SpTraceCallback_t icb, SpTraceCallback_t dcb,
		     void* ut, uint32_t code)
	: m_initcb(icb), m_dumpcb(dcb), m_userthis(ut), m_code(code) {};
};

//=============================================================================
//=============================================================================
//=============================================================================
// Opening/Closing

void SpTraceVcd::open (const char* filename) {
    if (m_isOpen) return;

    // Assertions, as we cast enum to uint32_t pointers in AutoTrace.pm
    enum SpTraceVcd_enumtest { FOO = 1 };
    if (sizeof(SpTraceVcd_enumtest) != sizeof(uint32_t)) {
	cerr << "%Error: SpTraceVcd::"<<__LINE__<<" cast assumption violated\n";
	abort();
    }

    // Set member variables
    m_filename = filename;
    s_vcdVecp.push_back(this);

    openNext (m_rolloverMB!=0);

    printStr("$version Generated by SpTraceVcd $end\n");
    time_t time_str = time(NULL);
    printStr("$date "); printStr(ctime(&time_str)); printStr(" $end\n");
    printStr("$timescale 1ns/1ns $end\n");
    
    definitions();
    for (uint32_t ent = 0; ent< m_callbacks.size(); ent++) {
	SpTraceCallInfo *cip = m_callbacks[ent];
	cip->m_code = nextCode();
	(cip->m_initcb) (this, cip->m_userthis, cip->m_code);
    }
    enddefinitions ();

    if (m_rolloverMB) {
	this->openNext(true);
    }
}

void SpTraceVcd::openNext (bool incFilename) {
    // Open next filename in concat sequence, mangle filename if
    // incFilename is true.
    close(); // Close existing
    if (incFilename) {
	// Find _0000.{ext} in filename
	string name = m_filename;
	int 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;
    }
    m_isOpen = true;
    if (m_filename[0]=='|') {
	assert(0);	// Not supported yet.
    } else {
	m_fd = ::open (m_filename.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0666);
    }
    m_fullDump = true;	// First dump must be full
}

SpTraceVcd::~SpTraceVcd() {
    close();
    if (m_wrBufp) { delete m_wrBufp; m_wrBufp=NULL; }
    // Remove from list of traces
    vector<SpTraceVcd*>::iterator pos = find(s_vcdVecp.begin(), s_vcdVecp.end(), this);
    if (pos != s_vcdVecp.end()) { s_vcdVecp.erase(pos); }
}

void SpTraceVcd::close () {
    if (!m_isOpen) return;

    bufferFlush();
    m_isOpen = false;
    ::close(m_fd);
}

void SpTraceVcd::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 SpTraceVcd::printStr (const char* str) {
    // Not fast...
    while (*str) {
	*m_writep++ = *str++;
	bufferCheck();
    }
}

void SpTraceVcd::printInt (int n) {
    char buf [100];
    sprintf(buf,"%d",n);
    printStr(buf);
}

void SpTraceVcd::bufferFlush () {
    // 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 then using buffered I/O
    if (!m_isOpen) return;
    char* wp = m_wrBufp;
    while (1) {
	size_t remaining = (m_writep - wp);
	if (remaining==0) break;
	int got = write (m_fd, wp, remaining);
	if (got>0) {
	    wp += got;
	} else if (got < 0) {
	    if (errno != EAGAIN && errno != EINTR) {
		/* write failed, presume error */
		perror("bin-write");
		break;
	    }
	}
    }

    // Reset buffer
    m_writep = m_wrBufp;
}

//=============================================================================
// Definitions

void SpTraceVcd::definitions () {
    assert (m_modDepth==0);
    printIndent(1);
    printStr("\n");
}

void SpTraceVcd::module (string name) {
    const char* curName = m_modName.c_str();
    const char* newName = name.c_str();

    // We detect the .'s in module names to determine
    // hiearchy.  This allows name() to be used inside
    // the module call.

    const char* cp = curName;
    const char* np = newName;
    for (; *np && (*np == *cp); np++, cp++) {
    }

    // Only allow split at hiearchy character
    while (np!=newName && *np && *np!='.') { np--; cp--; }
    if (*np=='.') np++;
    if (*cp=='.') cp++;

    for (; *cp; cp++) {
	if (cp[1]=='\0' || cp[1]=='.') {
	    printIndent(-1);
	    printStr("$upscope $end\n");
	}
    }

    while (*np) {
	printIndent(1);
	printStr("$scope module ");
	for (; *np && *np!='.'; np++) {
	    if (*np=='[') printStr("(");
	    else if (*np==']') printStr(")");
	    else *m_writep++=*np;
	}
	if (*np=='.') np++;
	printStr(" $end\n");
    }

    m_modName = name;
}

//void SpTraceVcd::endmodule ()
//{
//}

void SpTraceVcd::declBit (uint32_t code, const char* name, int arraynum,
			  const bool* valp)
{  declare (code, name, arraynum, valp, 0, 0, true, sizeof(*valp)); }
void SpTraceVcd::declBit (uint32_t code, const char* name, int arraynum,
			  const uint8_t* valp)
{  declare (code, name, arraynum, valp, 0, 0, true, sizeof(*valp)); }
void SpTraceVcd::declBit (uint32_t code, const char* name, int arraynum,
			  const uint32_t* valp)
{  declare (code, name, arraynum, valp, 0, 0, true, sizeof(*valp)); }
void SpTraceVcd::declBus (uint32_t code, const char* name, int arraynum,
			  const uint8_t* valp, int msb, int lsb)
{  declare (code, name, arraynum, valp, msb, lsb, false, sizeof(*valp)); }
void SpTraceVcd::declBus (uint32_t code, const char* name, int arraynum,
			  const uint32_t* valp, int msb, int lsb)
{  declare (code, name, arraynum, valp, msb, lsb, false, sizeof(*valp)); }
void SpTraceVcd::declArray (uint32_t code, const char* name, int arraynum,
			    const uint32_t* valp, int msb, int lsb)
{  declare (code, name, arraynum, valp, msb, lsb, false, sizeof(*valp)); }

void SpTraceVcd::declare (uint32_t code, const char* name,
			  int arraynum,
			  const void* valp,
			  int msb, int lsb,
			  bool isBit,
			  int storage)
{
    if (!code) { cerr<<"%Error: internal trace problem, code 0 is illegal\n"; abort(); }
    printIndent(0);

    // Stash info
    if (m_sigs.capacity() <= code
	|| m_sigs_oldval.capacity() <= code) {
	m_sigs.reserve(code*2);	// Power-of-2 allocation speeds things up
	m_sigs_oldval.reserve(code*2);
    }
    SpTraceVcdSig sig = SpTraceVcdSig(code, valp, (msb-lsb+1), storage);
    m_sigs.push_back(sig);

    // Print reference
    printStr("$var wire ");
    printInt(msb-lsb+1);
    printStr(" ");
    printCode(code);
    printStr(" ");
    printStr(name);
    if (arraynum>=0) {
	printStr("(");
	printInt(arraynum);
	printStr(")");
    }
    if (isBit) {
	printStr(" $end\n");
    } else {
	printStr(" [");
	printInt(msb);
	printStr(":");
	printInt(lsb);
	printStr("] $end\n");
    }

    m_nextCode = max(nextCode(), code+1+int((msb-lsb+1)/32));
}

void SpTraceVcd::enddefinitions () {
    module("");

    printIndent(-1);
    printStr("$enddefinitions $end\n\n\n");
    assert (m_modDepth==0);
}

//=============================================================================
// Callbacks

void SpTraceVcd::addCallback (
    SpTraceCallback_t initcb, SpTraceCallback_t dumpcb,
    void* userthis)
{
    if (m_isOpen) {
	cerr << "%Error: SpTraceVcd::"<<__FUNCTION__<<" called with already open file\n";
	abort();
    }
    SpTraceCallInfo* vci = new SpTraceCallInfo(initcb, dumpcb, userthis, nextCode());
    m_callbacks.push_back(vci);
}

//=============================================================================
// Dumping

void SpTraceVcd::dumpFull (double timestamp) {
    dumpPrep (timestamp);
    for (uint32_t ent = 0; ent< m_sigs.size(); ent++) {
	uint32_t code = m_sigs[ent].m_code;
	int bits = m_sigs[ent].m_bits;
	int storage = m_sigs[ent].m_storage;
	if (storage==4) {
	    const uint32_t* valp = (const uint32_t*)m_sigs[ent].m_valp;
	    if (bits==1) {
		dumpValueBit  (code, *valp);
	    } else if (bits<=32) {
		dumpValueBus  (code, *valp, bits);
	    } else {
		dumpValueArray(code, valp, bits);
	    }
	} else {
	    const uint8_t* valp = (const uint8_t*)m_sigs[ent].m_valp;
	    if (bits==1) {
		dumpValueBit  (code, *valp);
	    } else if (bits<=32) {
		dumpValueBus  (code, *valp, bits);
	    } else {
		abort(); // Unsupported
	    }
	}
    }
    dumpDone ();
}

void SpTraceVcd::dump (double timestamp) {
    if (!m_isOpen) return;
    if (m_fullDump) {
	m_fullDump = false;	// No need for more full dumps
	dumpFull(timestamp);
	return;
    }
    if (m_rolloverMB && (lseek(m_fd, 0, SEEK_CUR)/1e6 >= this->m_rolloverMB)) this->openNext(true);
    dumpPrep (timestamp);
    for (uint32_t ent = 0; ent< m_callbacks.size(); ent++) {
	SpTraceCallInfo *cip = m_callbacks[ent];
	(cip->m_dumpcb) (this, cip->m_userthis, cip->m_code);
    }
    dumpDone();
}

void SpTraceVcd::dumpPrep (double timestamp) {
    printStr("#");
    printInt((int)timestamp);
    printStr("\n");
}

void SpTraceVcd::dumpDone () {
}

//=============================================================================
// SpTraceVcd

const char* genId () {
    static char vcd_var_id_number[4] = {1,0,0,0};   /* one number for each ascii character */
    static char pistr[5];

    for(int i=0;i<4;i++){
	if (vcd_var_id_number[i]==0) pistr[i] = 0;
	else pistr[i] = (vcd_var_id_number[i]+32);
    }
    pistr[4] = 0;
    
    /* valid character from decimal 33 to 126 (space=32)*/
    vcd_var_id_number[0]++;
    for(int i=0;i<3;i++){
	if (vcd_var_id_number[i]>126){
	    vcd_var_id_number[i+1]++;
	    vcd_var_id_number[i] = 1;
	}
    }
    assert(vcd_var_id_number[3]<127);

    return (pistr);
}

//======================================================================
// Static members

void SpTraceVcd::flush_all() {
    for (uint32_t ent = 0; ent< s_vcdVecp.size(); ent++) {
	SpTraceVcd* vcdp = s_vcdVecp[ent];
	vcdp->flush();
    }
}

//======================================================================
//======================================================================
// Helper

#ifndef SPTRACEVCD_TEST

void SpTraceFile::write_comment (const sc_string &) {}
void SpTraceFile::trace (const unsigned int &, const sc_string &, const char **) {}

#define DECL_TRACE_METHOD_A(tp) \
    void SpTraceFile::trace( const tp& object, const sc_string& name ) {}
#define DECL_TRACE_METHOD_B(tp) \
    void SpTraceFile::trace( const tp& object, const sc_string& name, int width ) {}

#if (SYSTEMC_VERSION>20011000)
    // SystemC 2.0.1
    DECL_TRACE_METHOD_A( bool )
    DECL_TRACE_METHOD_A( sc_bit )
    DECL_TRACE_METHOD_A( sc_logic )
    DECL_TRACE_METHOD_B( unsigned char )
    DECL_TRACE_METHOD_B( unsigned short )
    DECL_TRACE_METHOD_B( unsigned int )
    DECL_TRACE_METHOD_B( unsigned long )
    DECL_TRACE_METHOD_B( char )
    DECL_TRACE_METHOD_B( short )
    DECL_TRACE_METHOD_B( int )
    DECL_TRACE_METHOD_B( long )
    DECL_TRACE_METHOD_A( float )
    DECL_TRACE_METHOD_A( double )
    DECL_TRACE_METHOD_A( sc_int_base )
    DECL_TRACE_METHOD_A( sc_uint_base )
    DECL_TRACE_METHOD_A( sc_signed )
    DECL_TRACE_METHOD_A( sc_unsigned )
    DECL_TRACE_METHOD_A( sc_fxval )
    DECL_TRACE_METHOD_A( sc_fxval_fast )
    DECL_TRACE_METHOD_A( sc_fxnum )
    DECL_TRACE_METHOD_A( sc_fxnum_fast )
    DECL_TRACE_METHOD_A( sc_bv_base )
    DECL_TRACE_METHOD_A( sc_lv_base )
#else
    // SystemC 1.2.1beta
    DECL_TRACE_METHOD_A( bool )
    DECL_TRACE_METHOD_B( unsigned char )
    DECL_TRACE_METHOD_B( short unsigned int )
    DECL_TRACE_METHOD_B( unsigned int )
    DECL_TRACE_METHOD_B( long unsigned int )
    DECL_TRACE_METHOD_B( char )
    DECL_TRACE_METHOD_B( short int )
    DECL_TRACE_METHOD_B( int )
    DECL_TRACE_METHOD_B( long int )
    DECL_TRACE_METHOD_A( float )
    DECL_TRACE_METHOD_A( double )
# ifndef _SC_LITE_
    DECL_TRACE_METHOD_A( sc_bit )
    DECL_TRACE_METHOD_A( sc_logic )
    DECL_TRACE_METHOD_A( sc_bool_vector )
    DECL_TRACE_METHOD_A( sc_logic_vector )
    DECL_TRACE_METHOD_A( sc_signal_bool_vector )
    DECL_TRACE_METHOD_A( sc_signal_logic_vector )
    DECL_TRACE_METHOD_A( sc_uint_base )
    DECL_TRACE_METHOD_A( sc_int_base )
    DECL_TRACE_METHOD_A( sc_unsigned )
    DECL_TRACE_METHOD_A( sc_signed )
    DECL_TRACE_METHOD_A( sc_signal_resolved )
    DECL_TRACE_METHOD_A( sc_signal_resolved_vector )
    DECL_TRACE_METHOD_A( sc_bv_ns::sc_bv_base )
    DECL_TRACE_METHOD_A( sc_bv_ns::sc_lv_base )
# endif
#endif

#undef DECL_TRACE_METHOD_A
#undef DECL_TRACE_METHOD_B

#endif

//======================================================================

#if SPTRACEVCD_TEST
uint32_t v1, v2, s1, s2[3];
uint8_t ch;
double timestamp = 1;

void dump (SpTraceVcd* vcdp, void* userthis, uint32_t code) {
    vcdp->dumpBus  (0x2, v1,5);
    vcdp->dumpBus  (0x3, v2,7);
    vcdp->dumpBit  (0x4, s1);
    vcdp->dumpBus  (0x5, ch,2);
    vcdp->dumpArray(0x6, &s2[0], 38);
    // Note need to add 3 for next code.
}

void init (SpTraceVcd* vcdp, void* userthis, uint32_t code) {
    vcdp->module ("top");
     vcdp->declBus (0x2, "v1",-1,&v1, 5,1);
     vcdp->declBus (0x3, "v2",-1,&v2, 6,0);
     vcdp->module ("top.sub1");
      vcdp->declBit (0x4, "s1",-1,&s1);
      vcdp->declBit (0x5, "ch",-1,&ch);
     vcdp->module ("top.sub2");
      vcdp->declArray (0x6, "s2",-1,&s2[0], 40,3);
}

main() {
    v1 = v2 = s1 = 0;
    s2[0] = s2[1] = s2[2] = 0;
    ch = 0;
  
    SpTraceVcd* vcdp = new SpTraceVcd;
    vcdp->addCallback (&init, &dump, 0);
    vcdp->open ("test.dump");
  
    // Dumping
    vcdp->dump(timestamp++);
    v1 = 0xfff;
    vcdp->dump(timestamp++);
    v2 = 0x1;
    s2[1] = 2;
    vcdp->dump(timestamp++);
    ch = 2;
    vcdp->dump(timestamp++);
    vcdp->close();
}
#endif

//********************************************************************
// Local Variables:
// compile-command: "cd ../test_dir && g++ -DSPTRACEVCD_TEST ../src/SpTraceVcd.cpp -o SpTraceVcd && ./SpTraceVcd && cat test.dump"
// End:
