OpenCores
URL https://opencores.org/ocsvn/wbscope/wbscope/trunk

Subversion Repositories wbscope

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /wbscope
    from Rev 12 to Rev 13
    Reverse comparison

Rev 12 → Rev 13

/trunk/Makefile
0,0 → 1,68
################################################################################
##
## Filename: Makefile
##
## Project: WBScope, a wishbone hosted scope
##
## Purpose: This is the master Makefile for the project. It coordinates
## the build of a Verilator test, "proving" that this core works
## (to the extent that any simulated test "proves" anything). This
## make file depends upon the proper setup of Verilator.
##
## Creator: Dan Gisselquist, Ph.D.
## Gisselquist Technology, LLC
##
################################################################################
##
## Copyright (C) 2015-2016, Gisselquist Technology, LLC
##
## This program is free software (firmware): you can redistribute it and/or
## modify it under the terms of the GNU General Public License as published
## by the Free Software Foundation, either version 3 of the License, or (at
## your option) any later version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 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 program. (It's in the $(ROOT)/doc directory. Run make with no
## target there if the PDF file isn't present.) If not, see
## <http://www.gnu.org/licenses/> for a copy.
##
## License: GPL, v3, as defined and found on www.gnu.org,
## http://www.gnu.org/licenses/gpl.html
##
##
################################################################################
##
##
all: rtl bench test
SUBMAKE := $(MAKE) --no-print-directory -C
 
.PHONY: doc
doc:
$(SUBMAKE) doc
 
.PHONY: rtl
rtl:
$(SUBMAKE) rtl
 
.PHONY: bench
bench: rtl
$(SUBMAKE) bench/rtl
$(SUBMAKE) bench/cpp
 
.PHONY: test
test: bench
$(SUBMAKE) bench/cpp test
 
.PHONY: clean
clean:
$(SUBMAKE) rtl clean
$(SUBMAKE) bench/rtl clean
$(SUBMAKE) bench/cpp clean
$(SUBMAKE) doc clean
 
 
/trunk/bench/cpp/Makefile
1,11 → 1,30
################################################################################
##
## Filename:
## Filename: bench/cpp/Makefile
##
## Project: WBScope, a wishbone hosted scope
##
## Purpose:
## Purpose: This file directs the build of a Verilator-based test bench to
## prove that the wbscope and wbscopc work. This build must be
## called after building in bench/rtl, since it depends upon the products
## of that build.
##
## Targets:
##
## all: Builds both wbscope_tb and wbscopc_tb
##
## clean: Cleans up all of the build products, together with the .vcd
## files, so you can start over from scratch.
##
## wbscope_tb: A test bench for the basic wishbone scope.
## Prints success or failure on the last line.
##
## wbscopc_tb: A test bench for the compressed wishbone scope.
## Prints success or failure on the last line.
##
## test: Runs both testbenches, printing success if both succeed, or
## failure if one of the two does not.
##
## Creator: Dan Gisselquist, Ph.D.
## Gisselquist Technology, LLC
##
31,19 → 50,30
## License: GPL, v3, as defined and found on www.gnu.org,
## http://www.gnu.org/licenses/gpl.html
##
##
################################################################################
##
##
all: wbscope_tb
all: wbscope_tb wbscopc_tb
CXX := g++
RTLD := ../rtl
ROBJD:= $(RTLD)/obj_dir
VROOT:= /usr/share/verilator
VINCS:= -I$(VROOT)
VERILATOR_ROOT ?= $(shell bash -c 'verilator -V|grep VERILATOR_ROOT| head -1|sed -e " s/^.*=\s*//"')
VROOT:= $(VERILATOR_ROOT)
INCS := -I$(VROOT)/include -I$(ROBJD)
VSRCS:= $(VROOT)/include/verilated.cpp $(VROOT)/include/verilated_vcd_c.cpp
TBOBJ:= $(ROBJD)/Vwbscope_tb__ALL.a
TCOBJ:= $(ROBJD)/Vwbscopc_tb__ALL.a
 
wbscope_tb: wbscope_tb.cpp $(ROBJD)/Vwbscope_tb__ALL.a $(ROBJD)/Vwbscope_tb.h
wbscope_tb: wbscope_tb.cpp $(TBOBJ) $(ROBJD)/Vwbscope_tb.h wb_tb.h testb.h
$(CXX) $(INCS) wbscope_tb.cpp $(VSRCS) $(TBOBJ) -o $@
 
wbscopc_tb: wbscopc_tb.cpp $(TCOBJ) $(ROBJD)/Vwbscopc_tb.h wb_tb.h testb.h
$(CXX) $(INCS) wbscopc_tb.cpp $(VSRCS) $(TCOBJ) -o $@
 
test: wbscope_tb wbscopc_tb
./wbscope_tb
./wbscopc_tb
 
clean:
rm -f wbscope_tb wbscopc_tb
rm -f wbscope_tb.vcd wbscopc_tb.vcd
/trunk/bench/cpp/devbus.h
0,0 → 1,148
////////////////////////////////////////////////////////////////////////////////
//
// Filename: devbus.h
//
// Project: WBScope, a wishbone hosted scope
//
// Purpose: The purpose of this file is to document an interface which
// any device with a bus, whether it be implemented over a UART,
// an ethernet, or a PCI express bus, must implement. This describes
// only an interface, and not how that interface is to be accomplished.
//
// The neat part of this interface is that, if programs are designed to
// work with it, than the implementation details may be changed later
// and any program that once worked with the interface should be able
// to continue to do so. (i.e., switch from a UART controlled bus to a
// PCI express controlled bus, with minimal change to the software of
// interest.)
//
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2015-2017, Gisselquist Technology, LLC
//
// This program is free software (firmware): you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 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 program. (It's in the $(ROOT)/doc directory. Run make with no
// target there if the PDF file isn't present.) If not, see
// <http://www.gnu.org/licenses/> for a copy.
//
// License: GPL, v3, as defined and found on www.gnu.org,
// http://www.gnu.org/licenses/gpl.html
//
//
////////////////////////////////////////////////////////////////////////////////
//
//
#ifndef DEVBUS_H
#define DEVBUS_H
 
#include <stdio.h>
#include <unistd.h>
 
typedef unsigned int uint32;
 
class BUSERR {
public:
uint32 addr;
BUSERR(const uint32 a) : addr(a) {};
};
 
class DEVBUS {
public:
typedef uint32 BUSW;
 
virtual void kill(void) = 0;
virtual void close(void) = 0;
 
// Write a single value to a single address
// a is the address of the value to be read as it exists on the
// wishbone bus within the FPGA.
// v is the singular value to write to this address
virtual void writeio(const BUSW a, const BUSW v) = 0;
 
// Read a single value to a single address
// a is the address of the value to be read as it exists on the
// wishbone bus within the FPGA.
// This function returns the value read from the device wishbone
// at address a.
virtual BUSW readio(const BUSW a) = 0;
 
// Read a series of values from values from a block of memory
// a is the address of the value to be read as it exists on the
// wishbone bus within the FPGA.
// len is the number of words to read
// buf is a pointer to a place to store the words once read.
// This is equivalent to:
// for(int i=0; i<len; i++)
// buf[i] = readio(a+i);
// only it's faster in our implementation.
virtual void readi(const BUSW a, const int len, BUSW *buf) = 0;
 
// Read a series of values from the same address in memory. This
// call is identical to readi, save that the address is not incremented
// from one read to the next. It is equivalent to:
// for(int i=0; i<len; i++)
// buf[i] = readio(a);
// only it's faster in our implementation.
//
virtual void readz(const BUSW a, const int len, BUSW *buf) = 0;
 
// Write a series of values into a block of memory on the FPGA
// a is the address of the value to be written as it exists on the
// wishbone bus within the FPGA.
// len is the number of words to write
// buf is a pointer to a place to from whence to grab the data
// to be written.
// This is equivalent to:
// for(int i=0; i<len; i++)
// writeio(a+i, buf[i]);
// only it's faster in our implementation.
virtual void writei(const BUSW a, const int len, const BUSW *buf) = 0;
// Write a series of values into the same address on the FPGA bus. This
// call is identical to writei, save that the address is not incremented
// from one write to the next. It is equivalent to:
// for(int i=0; i<len; i++)
// writeio(a, buf[i]);
// only it's faster in our implementation.
//
virtual void writez(const BUSW a, const int len, const BUSW *buf) = 0;
 
// Query whether or not an interrupt has taken place
virtual bool poll(void) = 0;
 
// Sleep until interrupt, but sleep no longer than msec milliseconds
virtual void usleep(unsigned msec) = 0;
 
// Sleep until an interrupt, no matter how long it takes for that
// interrupt to take place
virtual void wait(void) = 0;
 
// Query whether or not a bus error has taken place. This is somewhat
// of a misnomer, as my current bus error detection code exits any
// interface, but ... it is what it is.
virtual bool bus_err(void) const = 0;
 
// Clear any bus error condition.
virtual void reset_err(void) = 0;
 
// Clear any interrupt condition that has already been noticed by
// the interface, does not check for further interrupt
virtual void clear(void) = 0;
 
virtual ~DEVBUS(void) { };
};
 
#endif
/trunk/bench/cpp/testb.h
2,7 → 2,7
//
// Filename: testb.h
//
// Project: Zip CPU -- a small, lightweight, RISC CPU core
// Project: WBScope, a wishbone hosted scope
//
// Purpose: A wrapper for a common interface to a clocked FPGA core
// begin exercised in Verilator.
25,7 → 25,7
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. (It's in the $(ROOT)/doc directory, run make with no
// with this program. (It's in the $(ROOT)/doc directory. Run make with no
// target there if the PDF file isn't present.) If not, see
// <http://www.gnu.org/licenses/> for a copy.
//
34,6 → 34,8
//
//
////////////////////////////////////////////////////////////////////////////////
//
//
#ifndef TESTB_H
#define TESTB_H
 
41,6 → 43,8
#include <stdint.h>
#include <verilated_vcd_c.h>
 
#define TBASSERT(TB,A) do { if (!(A)) { (TB).closetrace(); } assert(A); } while(0);
 
template <class VA> class TESTB {
public:
VA *m_core;
50,22 → 54,27
TESTB(void) : m_trace(NULL), m_tickcount(0l) {
m_core = new VA;
Verilated::traceEverOn(true);
m_core->i_clk = 0;
eval(); // Get our initial values set properly.
}
virtual ~TESTB(void) {
if (m_trace) m_trace->close();
closetrace();
delete m_core;
m_core = NULL;
}
 
virtual void opentrace(const char *vcdname) {
m_trace = new VerilatedVcdC;
m_core->trace(m_trace, 99);
m_trace->open(vcdname);
if (!m_trace) {
m_trace = new VerilatedVcdC;
m_core->trace(m_trace, 99);
m_trace->open(vcdname);
}
}
 
virtual void closetrace(void) {
if (m_trace) {
m_trace->close();
delete m_trace;
m_trace = NULL;
}
}
77,16 → 86,22
virtual void tick(void) {
m_tickcount++;
 
//if((m_trace)&&(m_tickcount)) m_trace->dump(10*m_tickcount-4);
// Make sure we have our evaluations straight before the top
// of the clock. This is necessary since some of the
// connection modules may have made changes, for which some
// logic depends. This forces that logic to be recalculated
// before the top of the clock.
eval();
if ((m_trace)&&(m_tickcount)) m_trace->dump(10*m_tickcount-2);
if (m_trace) m_trace->dump(10*m_tickcount-2);
m_core->i_clk = 1;
eval();
if (m_trace) m_trace->dump(10*m_tickcount);
m_core->i_clk = 0;
eval();
if (m_trace) m_trace->dump(10*m_tickcount+5);
 
if (m_trace) {
m_trace->dump(10*m_tickcount+5);
m_trace->flush();
}
}
 
virtual void reset(void) {
/trunk/bench/cpp/wb_tb.h
0,0 → 1,455
////////////////////////////////////////////////////////////////////////////////
//
// Filename: wb_tb.cpp
//
// Project: WBScope, a wishbone hosted scope
//
// Purpose: To provide a fairly generic interface wrapper to a wishbone bus,
// that can then be used to create a test-bench class.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2015-2017, Gisselquist Technology, LLC
//
// This program is free software (firmware): you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 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 program. (It's in the $(ROOT)/doc directory. Run make with no
// target there if the PDF file isn't present.) If not, see
// <http://www.gnu.org/licenses/> for a copy.
//
// License: GPL, v3, as defined and found on www.gnu.org,
// http://www.gnu.org/licenses/gpl.html
//
//
////////////////////////////////////////////////////////////////////////////////
//
//
#include <stdio.h>
#include <stdlib.h>
 
#include <verilated.h>
#include <verilated_vcd_c.h>
#include "testb.h"
#include "devbus.h"
 
const int BOMBCOUNT = 32;
 
template <class VA> class WB_TB : public TESTB<VA>, public DEVBUS {
#ifdef WBERR
bool m_buserr;
#endif
#ifdef INTERRUPTWIRE
bool m_interrupt;
#endif
public:
typedef uint32_t BUSW;
bool m_bomb;
 
WB_TB(void) {
m_bomb = false;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
#ifdef WBERR
m_buserr = false;
#endif
#ifdef INTERRUPTWIRE
m_interrupt = false;
#endif
}
 
virtual void close(void) {
TESTB<VA>::closetrace();
}
 
virtual void kill(void) {
close();
}
 
#ifdef INTERRUPTWIRE
virtual void tick(void) {
TESTB<VA>::tick();
if (TESTB<VA>::m_core->INTERRUPTWIRE)
m_interrupt = true;
}
#endif
#define TICK this->tick
 
void idle(const unsigned counts = 1) {
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
for(unsigned k=0; k<counts; k++) {
this->tick();
assert(!TESTB<VA>::m_core->o_wb_ack);
}
}
 
BUSW readio(BUSW a) {
int errcount = 0;
BUSW result;
 
// printf("WB-READM(%08x)\n", a);
 
TESTB<VA>::m_core->i_wb_cyc = 1;
TESTB<VA>::m_core->i_wb_stb = 1;
TESTB<VA>::m_core->i_wb_we = 0;
TESTB<VA>::m_core->i_wb_addr= (a>>2);
 
if (TESTB<VA>::m_core->o_wb_stall) {
while((errcount++ < BOMBCOUNT)&&(TESTB<VA>::m_core->o_wb_stall)) {
TICK();
#ifdef WBERR
if (TESTB<VA>::m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return -1;
}
#endif
}
} TICK();
 
TESTB<VA>::m_core->i_wb_stb = 0;
 
while((errcount++ < BOMBCOUNT)&&(!TESTB<VA>::m_core->o_wb_ack)) {
TICK();
#ifdef WBERR
if (TESTB<VA>::m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return -1;
}
#endif
}
 
 
result = TESTB<VA>::m_core->o_wb_data;
 
// Release the bus
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
 
if(errcount >= BOMBCOUNT) {
printf("WB/SR-BOMB: NO RESPONSE AFTER %d CLOCKS\n", errcount);
m_bomb = true;
} else if (!TESTB<VA>::m_core->o_wb_ack) {
printf("WB/SR-BOMB: NO ACK, NO TIMEOUT\n");
m_bomb = true;
}
TICK();
 
assert(!TESTB<VA>::m_core->o_wb_ack);
assert(!TESTB<VA>::m_core->o_wb_stall);
 
return result;
}
 
void readv(const BUSW a, int len, BUSW *buf, const int inc=1) {
int errcount = 0;
int THISBOMBCOUNT = BOMBCOUNT * len;
int cnt, rdidx;
 
printf("WB-READM(%08x, %d)\n", a, len);
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
 
while((errcount++ < BOMBCOUNT)&&(TESTB<VA>::m_core->o_wb_stall))
TICK();
 
if (errcount >= BOMBCOUNT) {
printf("WB-READ(%d): Setting bomb to true (errcount = %d)\n", __LINE__, errcount);
m_bomb = true;
return;
}
 
errcount = 0;
TESTB<VA>::m_core->i_wb_cyc = 1;
TESTB<VA>::m_core->i_wb_stb = 1;
TESTB<VA>::m_core->i_wb_we = 0;
TESTB<VA>::m_core->i_wb_addr = (a>>2);
 
rdidx =0; cnt = 0;
 
do {
int s;
TESTB<VA>::m_core->i_wb_stb = ((rand()&7)!=0) ? 1:0;
s = ((TESTB<VA>::m_core->i_wb_stb)
&&(TESTB<VA>::m_core->o_wb_stall==0))?0:1;
TICK();
TESTB<VA>::m_core->i_wb_addr += (inc&(s^1))?4:0;
cnt += (s^1);
if (TESTB<VA>::m_core->o_wb_ack)
buf[rdidx++] = TESTB<VA>::m_core->o_wb_data;
#ifdef WBERR
if (TESTB<VA>::m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return -1;
}
#endif
} while((cnt < len)&&(errcount++ < THISBOMBCOUNT));
 
TESTB<VA>::m_core->i_wb_stb = 0;
 
while((rdidx < len)&&(errcount++ < THISBOMBCOUNT)) {
TICK();
if (TESTB<VA>::m_core->o_wb_ack)
buf[rdidx++] = TESTB<VA>::m_core->o_wb_data;
#ifdef WBERR
if (TESTB<VA>::m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return -1;
}
#endif
}
 
// Release the bus
TESTB<VA>::m_core->i_wb_cyc = 0;
 
if(errcount >= THISBOMBCOUNT) {
printf("WB/PR-BOMB: NO RESPONSE AFTER %d CLOCKS\n", errcount);
m_bomb = true;
} else if (!TESTB<VA>::m_core->o_wb_ack) {
printf("WB/PR-BOMB: NO ACK, NO TIMEOUT\n");
m_bomb = true;
}
TICK();
assert(!TESTB<VA>::m_core->o_wb_ack);
}
 
void readi(const BUSW a, const int len, BUSW *buf) {
return readv(a, len, buf, 1);
}
 
void readz(const BUSW a, const int len, BUSW *buf) {
return readv(a, len, buf, 0);
}
 
void writeio(const BUSW a, const BUSW v) {
int errcount = 0;
 
printf("WB-WRITEM(%08x) <= %08x\n", a, v);
TESTB<VA>::m_core->i_wb_cyc = 1;
TESTB<VA>::m_core->i_wb_stb = 1;
TESTB<VA>::m_core->i_wb_we = 1;
TESTB<VA>::m_core->i_wb_addr= (a>>2);
TESTB<VA>::m_core->i_wb_data= v;
// TESTB<VA>::m_core->i_wb_sel = 0x0f;
 
if (TESTB<VA>::m_core->o_wb_stall)
while((errcount++ < BOMBCOUNT)&&(TESTB<VA>::m_core->o_wb_stall)) {
printf("Stalled, so waiting, errcount=%d\n", errcount);
TICK();
#ifdef WBERR
if (m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return;
}
#endif
}
TICK();
#ifdef WBERR
if (m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return;
}
#endif
 
TESTB<VA>::m_core->i_wb_stb = 0;
 
while((errcount++ < BOMBCOUNT)&&(!TESTB<VA>::m_core->o_wb_ack)) {
TICK();
#ifdef WBERR
if (m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return;
}
#endif
}
TICK();
 
// Release the bus?
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
 
if(errcount >= BOMBCOUNT) {
printf("WB/SW-BOMB: NO RESPONSE AFTER %d CLOCKS (LINE=%d)\n",errcount, __LINE__);
m_bomb = true;
} TICK();
#ifdef WBERR
if (m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return;
}
#endif
assert(!TESTB<VA>::m_core->o_wb_ack);
assert(!TESTB<VA>::m_core->o_wb_stall);
}
 
void writev(const BUSW a, const int ln, const BUSW *buf, const int inc=1) {
unsigned errcount = 0, nacks = 0;
 
printf("WB-WRITEM(%08x, %d, ...)\n", a, ln);
TESTB<VA>::m_core->i_wb_cyc = 1;
TESTB<VA>::m_core->i_wb_stb = 1;
TESTB<VA>::m_core->i_wb_we = 1;
TESTB<VA>::m_core->i_wb_addr= (a>>2);
// TESTB<VA>::m_core->i_wb_sel = 0x0f;
for(unsigned stbcnt=0; stbcnt<ln; stbcnt++) {
// m_core->i_wb_addr= a+stbcnt;
TESTB<VA>::m_core->i_wb_data= buf[stbcnt];
errcount = 0;
 
while((errcount++ < BOMBCOUNT)&&(TESTB<VA>::m_core->o_wb_stall)) {
TICK();
if (TESTB<VA>::m_core->o_wb_ack)
nacks++;
#ifdef WBERR
if (m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return;
}
#endif
}
// Tick, now that we're not stalled. This is the tick
// that gets accepted.
TICK();
if (TESTB<VA>::m_core->o_wb_ack) nacks++;
#ifdef WBERR
if (m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return;
}
#endif
 
// Now update the address
TESTB<VA>::m_core->i_wb_addr += (inc)?4:0;
}
 
TESTB<VA>::m_core->i_wb_stb = 0;
 
errcount = 0;
while((nacks < ln)&&(errcount++ < BOMBCOUNT)) {
TICK();
if (TESTB<VA>::m_core->o_wb_ack) {
nacks++;
errcount = 0;
}
#ifdef WBERR
if (m_core->WBERR) {
m_buserr = true;
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
return;
}
#endif
}
 
// Release the bus
TESTB<VA>::m_core->i_wb_cyc = 0;
TESTB<VA>::m_core->i_wb_stb = 0;
 
if(errcount >= BOMBCOUNT) {
printf("WB/PW-BOMB: NO RESPONSE AFTER %d CLOCKS (LINE=%d)\n",errcount,__LINE__);
m_bomb = true;
}
TICK();
assert(!TESTB<VA>::m_core->o_wb_ack);
assert(!TESTB<VA>::m_core->o_wb_stall);
}
 
void writei(const BUSW a, const int ln, const BUSW *buf) {
writev(a, ln, buf, 1);
}
 
void writez(const BUSW a, const int ln, const BUSW *buf) {
writev(a, ln, buf, 0);
}
 
 
bool bombed(void) const { return m_bomb; }
 
// bool debug(void) const { return m_debug; }
// bool debug(bool nxtv) { return m_debug = nxtv; }
 
bool poll(void) {
#ifdef INTERRUPTWIRE
return (m_interrupt)||(TESTB<VA>::m_core->INTERRUPTWIRE != 0);
#else
return false;
#endif
}
 
bool bus_err(void) const {
#ifdef WBERR
return m_buserr;
#else
return false;
#endif
}
 
void reset_err(void) {
#ifdef WBERR
m_buserr = false;;
#endif
}
 
void usleep(unsigned msec) {
#ifdef CLKRATEHZ
unsigned count = CLKRATEHZ / 1000 * msec;
#else
// Assume 100MHz if no clockrate is given
unsigned count = 1000*100 * msec;
#endif
while(count-- != 0)
#ifdef INTERRUPTWIRE
if (poll()) return; else
#endif
TICK();
}
 
void clear(void) {
#ifdef INTERRUPTWIRE
m_interrupt = false;
#endif
}
 
void wait(void) {
#ifdef INTERRUPTWIRE
while(!poll())
TICK();
#else
assert(("No interrupt defined",0));
#endif
}
};
 
/trunk/bench/cpp/wbscopc_tb.cpp
0,0 → 1,195
////////////////////////////////////////////////////////////////////////////////
//
// Filename: wbscopc_tb.cpp
//
// Project: WBScope, a wishbone hosted scope
//
// Purpose: A quick test bench to determine if the run-length encoded
// wbscopc module works.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2015-2017, Gisselquist Technology, LLC
//
// This program is free software (firmware): you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 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 program. (It's in the $(ROOT)/doc directory. Run make with no
// target there if the PDF file isn't present.) If not, see
// <http://www.gnu.org/licenses/> for a copy.
//
// License: GPL, v3, as defined and found on www.gnu.org,
// http://www.gnu.org/licenses/gpl.html
//
//
////////////////////////////////////////////////////////////////////////////////
//
//
#include <stdio.h>
 
#include <verilated.h>
#include <verilated_vcd_c.h>
#include "Vwbscopc_tb.h"
#include "testb.h"
#include "devbus.h"
#define INTERRUPTWIRE o_interrupt
#include "wb_tb.h"
 
const int LGMEMSIZE = 15;
 
class WBSCOPC_TB : public WB_TB<Vwbscopc_tb> {
bool m_debug;
public:
 
WBSCOPC_TB(void) {
m_debug = true;
}
 
void tick(void) {
 
WB_TB<Vwbscopc_tb>::tick();
 
bool writeout = true;
if ((m_debug)&&(writeout)) {}
}
 
void reset(void) {
m_core->i_rst = 1;
m_core->i_wb_cyc = 0;
m_core->i_wb_stb = 0;
tick();
m_core->i_rst = 0;
}
 
unsigned trigger(void) {
m_core->i_trigger = 1;
idle();
m_core->i_trigger = 0;
printf("TRIGGERED AT %08x\n", m_core->o_data);
return m_core->o_data;
}
 
bool debug(void) const { return m_debug; }
bool debug(bool nxtv) { return m_debug = nxtv; }
};
 
int main(int argc, char **argv) {
Verilated::commandArgs(argc, argv);
WBSCOPC_TB *tb = new WBSCOPC_TB;
unsigned v, addr, trigger_addr;
unsigned *buf;
int trigpt;
 
tb->opentrace("wbscopc_tb.vcd");
printf("Giving the core 2 cycles to start up\n");
// Before testing, let's give the unit time enough to warm up
tb->reset();
tb->idle(2);
 
#define WBSCOPE_STATUS 0
#define WBSCOPE_DATA 4
#define WBSCOPE_NORESET 0x80000000
#define WBSCOPE_TRIGGER (WBSCOPE_NO_RESET|0x08000000)
#define WBSCOPE_MANUAL (WBSCOPE_TRIGGER)
#define WBSCOPE_PRIMED 0x10000000
#define WBSCOPE_TRIGGERED 0x20000000
#define WBSCOPE_STOPPED 0x40000000
#define WBSCOPE_DISABLED 0x04000000
#define WBSCOPE_LGLEN(A) ((A>>20)&0x01f)
#define WBSCOPE_LENGTH(A) (1<<(LGLEN(A)))
 
// First test ... read the status register
v = tb->readio(WBSCOPE_STATUS);
int ln = WBSCOPE_LGLEN(v);
printf("V = %08x\n", v);
printf("LN = %d, or %d entries\n", ln, (1<<ln));
printf("DLY = %d\n", (v&0xfffff));
if (((1<<ln) < tb->m_tickcount)&&(v&0x10000000)) {
printf("SCOPE is already triggered! ??\n");
goto test_failure;
}
buf = new unsigned[(1<<ln)];
 
tb->idle((1<<(12+4)) + (1<<ln) +240);
 
v = tb->readio(WBSCOPE_STATUS);
if ((v&WBSCOPE_PRIMED)==0) {
printf("v = %08x\n", v);
printf("SCOPE hasn\'t primed! ??\n");
goto test_failure;
}
 
tb->trigger();
v = tb->readio(WBSCOPE_STATUS);
if ((v&WBSCOPE_TRIGGERED)==0) {
printf("v = %08x\n", v);
printf("SCOPE hasn\'t triggered! ??\n");
goto test_failure;
}
 
while((v & WBSCOPE_STOPPED)==0)
v = tb->readio(WBSCOPE_STATUS);
printf("SCOPE has stopped, reading data\n");
 
tb->readz(WBSCOPE_DATA, (1<<ln), buf);
addr = 0;
trigger_addr = 0xffffffff;
for(int i=0; i<(1<<ln); i++) {
if (buf[i] & 0x80000000)
addr += (buf[i]&0x7fffffff) + 1;
else {
if ((i > 0)&&(buf[i-1]&0x80000000))
printf(" [*****]:\n");
printf("%5d[%5d]: %08x", addr, i, buf[i]);
if (buf[i] & 0x40000000) {
printf(" <<--- TRIGGER!");
trigger_addr = addr;
} printf("\n");
 
addr++;
}
} if ((buf[(1<<ln)-1]&0x80000000))
printf(" [*****]:\n");
 
if (buf[(1<<ln)-1] & 0x80000000) {
printf("ERR: LAST VALUE IS A RUN, 0x%08x\n", buf[(1<<ln)-1]);
goto test_failure;
}
 
if (trigger_addr == 0xffffffff) {
printf("ERR: TRIGGER NOT FOUND IN THE DATA!\n");
goto test_failure;
}
 
printf("TRIGGER ADDRESS = %08x (%5d)\n", trigger_addr, trigger_addr);
printf("V = %08x\n", v & 0x0fffff);
printf("Difference = %08x (%5d)\n", addr - trigger_addr,
addr - trigger_addr);
if (addr - 1 - trigger_addr != (v & 0x0fffff)) {
printf("TRIGGER AT THE WRONG LOCATION!\n");
goto test_failure;
}
 
printf("SUCCESS!!\n");
delete tb;
exit(0);
test_failure:
printf("FAIL-HERE\n");
for(int i=0; i<4; i++)
tb->tick();
printf("TEST FAILED\n");
delete tb;
exit(-1);
}
/trunk/bench/cpp/wbscope_tb.cpp
39,30 → 39,24
 
#include <verilated.h>
#include <verilated_vcd_c.h>
#include "Vwbscope_tb.h"
#include "testb.h"
#include "Vwbscope_tb.h"
#define INTERRUPTWIRE o_interrupt
#include "wb_tb.h"
 
#define MMUFLAG_RONW 8 // Read only (not writeable)
#define MMUFLAG_ACCS 4 // Accessed
#define MMUFLAG_CCHE 2 // Cachable
#define MMUFLAG_THSP 1 // Page has this context
const int LGMEMSIZE = 15;
 
const int BOMBCOUNT = 32,
LGMEMSIZE = 15;
 
class WBSCOPE_TB : public TESTB<Vwbscope_tb> {
bool m_bomb, m_miss, m_err, m_debug;
int m_last_tlb_index;
class WBSCOPE_TB : public WB_TB<Vwbscope_tb> {
bool m_bomb, m_debug;
public:
 
WBSCOPE_TB(void) {
m_debug = true;
m_last_tlb_index = 0;
}
 
void tick(void) {
 
TESTB<Vwbscope_tb>::tick();
WB_TB<Vwbscope_tb>::tick();
 
bool writeout = true;
if ((m_debug)&&(writeout)) {}
76,113 → 70,10
m_core->i_rst = 0;
}
 
void wb_tick(void) {
m_core->i_wb_cyc = 0;
m_core->i_wb_stb = 0;
tick();
assert(!m_core->o_wb_ack);
}
 
unsigned wb_read(unsigned a) {
unsigned result;
 
printf("WB-READM(%08x)\n", a);
 
m_core->i_wb_cyc = 1;
m_core->i_wb_stb = 1;
m_core->i_wb_we = 0;
m_core->i_wb_addr= (a>>2)&1;
 
// Dont need to check for stalls, since the wbscope never stalls
tick();
 
m_core->i_wb_stb = 0;
 
while(!m_core->o_wb_ack)
tick();
 
result = m_core->o_wb_data;
 
// Release the bus?
m_core->i_wb_cyc = 0;
m_core->i_wb_stb = 0;
 
// Let the bus idle for one cycle
tick();
 
return result;
}
 
void wb_read(unsigned a, int len, unsigned *buf) {
int cnt, rdidx;
 
printf("WB-READM(%08x, %d)\n", a, len);
 
m_core->i_wb_cyc = 1;
m_core->i_wb_stb = 1;
m_core->i_wb_we = 0;
m_core->i_wb_addr = (a>>2)&1;
 
rdidx =0; cnt = 0;
 
do {
tick();
// Normally, we'd increment the address here. For the
// scope, multiple reads only make sense if they are
// from the same address, hence we don't increment the
// address here
// m_core->i_wb_addr += inc;
cnt += 1;
if (m_core->o_wb_ack)
buf[rdidx++] = m_core->o_wb_data;
} while(cnt < len);
 
m_core->i_wb_stb = 0;
 
while(rdidx < len) {
tick();
if (m_core->o_wb_ack)
buf[rdidx++] = m_core->o_wb_data;
}
 
// Release the bus?
m_core->i_wb_cyc = 0;
 
tick();
assert(!m_core->o_wb_ack);
}
 
void wb_write(unsigned a, unsigned v) {
int errcount = 0;
 
printf("WB-WRITEM(%08x) <= %08x\n", a, v);
m_core->i_wb_cyc = 1;
m_core->i_wb_stb = 1;
m_core->i_wb_we = 1;
m_core->i_wb_addr= (a>>2)&1;
m_core->i_wb_data= v;
 
tick();
m_core->i_wb_stb = 0;
 
while(!m_core->o_wb_ack) {
tick();
}
 
tick();
 
// Release the bus?
m_core->i_wb_cyc = 0;
m_core->i_wb_stb = 0;
 
assert(!m_core->o_wb_ack);
}
 
unsigned trigger(void) {
m_core->i_trigger = 1;
wb_tick();
idle();
m_core->i_trigger = 0;
printf("TRIGGERED AT %08x\n", m_core->o_data);
return m_core->o_data;
}
 
196,13 → 87,13
unsigned v;
unsigned *buf;
int trigpt;
unsigned trigger_time, expected_first_value;
 
tb->opentrace("wbscope_tb.vcd");
printf("Giving the core 2 cycles to start up\n");
// Before testing, let's give the unit time enough to warm up
tb->reset();
for(int i=0; i<2; i++)
tb->wb_tick();
tb->idle(2);
 
#define WBSCOPE_STATUS 0
#define WBSCOPE_DATA 4
217,7 → 108,7
#define WBSCOPE_LENGTH(A) (1<<(LGLEN(A)))
 
// First test ... read the status register
v = tb->wb_read(WBSCOPE_STATUS);
v = tb->readio(WBSCOPE_STATUS);
int ln = WBSCOPE_LGLEN(v);
printf("V = %08x\n", v);
printf("LN = %d, or %d entries\n", ln, (1<<ln));
228,10 → 119,9
}
buf = new unsigned[(1<<ln)];
 
for(int i=0; i<(1<<ln); i++)
tb->wb_tick();
tb->idle(1<<ln);
 
v = tb->wb_read(WBSCOPE_STATUS);
v = tb->readio(WBSCOPE_STATUS);
if ((v&WBSCOPE_PRIMED)==0) {
printf("v = %08x\n", v);
printf("SCOPE hasn\'t primed! ??\n");
238,8 → 128,10
goto test_failure;
}
 
tb->trigger();
v = tb->wb_read(WBSCOPE_STATUS);
trigger_time = tb->trigger() & 0x7fffffff;
printf("TRIGGERED AT %08x\n", trigger_time);
 
v = tb->readio(WBSCOPE_STATUS);
if ((v&WBSCOPE_TRIGGERED)==0) {
printf("v = %08x\n", v);
printf("SCOPE hasn\'t triggered! ??\n");
247,17 → 139,21
}
 
while((v & WBSCOPE_STOPPED)==0)
v = tb->wb_read(WBSCOPE_STATUS);
v = tb->readio(WBSCOPE_STATUS);
printf("SCOPE has stopped, reading data\n");
 
tb->wb_read(WBSCOPE_DATA, (1<<ln), buf);
tb->readz(WBSCOPE_DATA, (1<<ln), buf);
for(int i=0; i<(1<<ln); i++) {
printf("%4d: %08x\n", i, buf[i]);
if ((i>0)&&(((buf[i]&0x7fffffff)-(buf[i-1]&0x7fffffff))!=1))
printf("%4d: %08x%s\n", i, buf[i],
(i== (1<<ln)-1-(v&0x0fffff)) ? " <<--- TRIGGER!":"");
if ((i>0)&&(((buf[i]&0x7fffffff)-(buf[i-1]&0x7fffffff))!=1)) {
printf("ERR: Scope data doesn't increment!\n");
printf("\tIn other words--its not matching the test signal\n");
goto test_failure;
}
}
 
trigpt = (1<<ln)-v&(0x0fffff);
trigpt = (1<<ln)-v&(0x0fffff)-1;
if ((trigpt >= 0)&&(trigpt < (1<<ln))) {
printf("Trigger value = %08x\n", buf[trigpt]);
if (((0x80000000 & buf[trigpt])==0)&&(trigpt>0)) {
269,6 → 165,14
}
}
 
expected_first_value = trigger_time + (v&0x0fffff) - (1<<ln);
if (buf[0] != expected_first_value) {
printf("Initial value = %08x\n", buf[0]);
printf("Expected: %08x\n", expected_first_value);
printf("ERR: WRONG STARTING-VALUE\n");
goto test_failure;
}
 
printf("SUCCESS!!\n");
delete tb;
exit(0);
/trunk/bench/rtl/Makefile
1,15 → 1,14
################################################################################
#
# Filename: Makefile
#
# Project: WBScope, a wishbone hosted scope
#
# Purpose: This makefile builds a verilator simulation of the rtl
# testbenches necessary to test certain components of the
# wishbone scope using Verilator. It does not make the system within
## Icarus, Vivado or Quartus.
##
## Filename: Makefile
##
## Project: WBScope, a wishbone hosted scope
##
## Purpose: This makefile builds a verilator simulation of the rtl
## testbenches necessary to test certain components of both the
## wishbone scope and its RLE compressed brother using Verilator.
##
##
## Creator: Dan Gisselquist, Ph.D.
## Gisselquist Technology, LLC
##
40,13 → 39,18
##
##
.PHONY: all
all: wbscope_tb
all: wbscope_tb wbscopc_tb
 
RTLD := ../../rtl
VOBJ := obj_dir
 
#
#
# Building the wbscope test bench
#
#
$(VOBJ)/Vwbscope_tb.cpp: $(RTLD)/wbscope.v wbscope_tb.v
verilator -trace -cc -y $(RTLD) wbscope_tb.v
verilator -Wall -O3 -trace -cc -y $(RTLD) wbscope_tb.v
$(VOBJ)/Vwbscope_tb.h: $(VOBJ)/Vwbscope_tb.cpp
 
$(VOBJ)/Vwbscope_tb__ALL.a: $(VOBJ)/Vwbscope_tb.cpp $(VOBJ)/Vwbscope_tb.h
55,6 → 59,21
.PHONY: wbscope_tb
wbscope_tb: $(VOBJ)/Vwbscope_tb__ALL.a
 
#
#
# Building the wbscopc test bench, for the compressed wbscope
#
#
$(VOBJ)/Vwbscopc_tb.cpp: $(RTLD)/wbscopc.v wbscopc_tb.v
verilator -Wall -O3 -trace -cc -y $(RTLD) wbscopc_tb.v
$(VOBJ)/Vwbscopc_tb.h: $(VOBJ)/Vwbscopc_tb.cpp
 
$(VOBJ)/Vwbscopc_tb__ALL.a: $(VOBJ)/Vwbscopc_tb.cpp $(VOBJ)/Vwbscopc_tb.h
make --no-print-directory --directory=$(VOBJ) -f Vwbscopc_tb.mk
 
.PHONY: wbscopc_tb
wbscopc_tb: $(VOBJ)/Vwbscopc_tb__ALL.a
 
# $(VOBJ)/Vaxiscope_tb.cpp: $(RTLD)/axiscope.v axiscope.v
# verilator -trace -cc -y $(RTLD) wbscope_tb.v
# $(VOBJ)/Vaxiscope_tb.h: $(VOBJ)/Vwbscope_tb.cpp
/trunk/bench/rtl/wbscopc_tb.v
0,0 → 1,92
////////////////////////////////////////////////////////////////////////////////
//
// Filename: wbscopc_tb.v
//
// Project: WBScope, a wishbone hosted scope
//
// Purpose: This file is a test bench wrapper around the compressed
// wishbone scope, designed to create a "signal" which can then
// be scoped and proven. Unlike the case of the normal wishbone scope,
// this scope needs a test signal that has lots of idle time surrounded
// my sudden changes. We'll handle our sudden changes via a counter.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2015-2017, Gisselquist Technology, LLC
//
// This program is free software (firmware): you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 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 program. (It's in the $(ROOT)/doc directory. Run make with no
// target there if the PDF file isn't present.) If not, see
// <http://www.gnu.org/licenses/> for a copy.
//
// License: GPL, v3, as defined and found on www.gnu.org,
// http://www.gnu.org/licenses/gpl.html
//
//
////////////////////////////////////////////////////////////////////////////////
//
//
module wbscope_tb(i_clk,
// i_rst is required by our test infrastructure, yet unused here
i_rst,
// The test data. o_data is internally generated here from
// o_counter, i_trigger is given externally
i_trigger, o_data, o_counter,
// Wishbone bus interaction
i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data,
// wishbone bus outputs
o_wb_ack, o_wb_stall, o_wb_data,
// And our output interrupt
o_interrupt);
input i_clk, i_rst, i_trigger;
output wire [30:0] o_data;
output wire [29:0] o_counter;
//
input i_wb_cyc, i_wb_stb, i_wb_we;
input i_wb_addr;
input [31:0] i_wb_data;
//
output wire o_wb_ack;
output wire [31:0] o_wb_data;
output wire o_wb_stall;
//
output o_interrupt;
 
reg [29:0] counter;
initial counter = 0;
always @(posedge i_clk)
counter <= counter + 1'b1;
always @(posedge i_clk)
if (counter[11:8] == 4'h0)
o_data <= { i_trigger, counter };
else if ((counter[10])&&(counter[1]))
o_data <= { i_trigger, counter };
else
o_data <= { i_trigger, counter[29:12], 12'h0 };
 
wire wb_stall_ignored;
 
wbscopc #(.LGMEM(5'd14), .BUSW(32), .SYNCHRONOUS(1), .MAX_STEP(768),
.DEFAULT_HOLDOFF(36))
scope(i_clk, 1'b1, i_trigger, o_data,
i_clk, i_wb_cyc, i_wb_stb, i_wb_we,
i_wb_addr, i_wb_data,
o_wb_ack, wb_stall_ignored, o_wb_data,
o_interrupt);
 
assign o_wb_stall = 1'b0;
 
endmodule
/trunk/bench/rtl/wbscope_tb.v
4,7 → 4,12
//
// Project: WBScope, a wishbone hosted scope
//
// Purpose:
// Purpose: This file is a test bench wrapper around the wishbone scope,
// designed to create a "signal" which can then be scoped and
// proven. In our case here, the "signal" is a counter. When we test
// the scope within our bench/cpp Verilator testbench, we'll know if our
// test was "correct" if the counter 1) only ever counts by 1, and 2) if
// the trigger lands on thte right data sample.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
35,9 → 40,18
////////////////////////////////////////////////////////////////////////////////
//
//
module wbscope_tb(i_clk, i_rst, i_trigger, o_data,
i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data,
o_wb_ack, o_wb_data, o_interrupt);
module wbscope_tb(i_clk,
// i_rst is required by our test infrastructure, yet unused here
i_rst,
// The test data. o_data is internally generated here from a
// counter, i_trigger is given externally
i_trigger, o_data,
// Wishbone bus interaction
i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data,
// wishbone bus outputs
o_wb_ack, o_wb_stall, o_wb_data,
// And our output interrupt
o_interrupt);
input i_clk, i_rst, i_trigger;
output wire [31:0] o_data;
//
46,6 → 60,7
input [31:0] i_wb_data;
//
output wire o_wb_ack;
output wire o_wb_stall;
output wire [31:0] o_wb_data;
//
output o_interrupt;
59,7 → 74,8
 
wire wb_stall_ignored;
 
wbscope #(5'd6, 32, 1)
wbscope #(.LGMEM(5'd6), .BUSW(32), .SYNCHRONOUS(1),
.DEFAULT_HOLDOFF(1))
scope(i_clk, 1'b1, i_trigger, o_data,
i_clk, i_wb_cyc, i_wb_stb, i_wb_we,
i_wb_addr, i_wb_data,
66,4 → 82,10
o_wb_ack, wb_stall_ignored, o_wb_data,
o_interrupt);
 
assign o_wb_stall = 1'b0;
 
// verilator lint_off UNUSED
wire [1:0] unused;
assign unused = { i_rst, wb_stall_ignored };
// verilator lint_on UNUSED
endmodule
/trunk/doc/spec.pdf Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream
/trunk/doc/src/spec.tex
19,7 → 19,7
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Copyright (C) 2015, Gisselquist Technology, LLC
%% Copyright (C) 2015,2017, Gisselquist Technology, LLC
%%
%% This program is free software (firmware): you can redistribute it and/or
%% modify it under the terms of the GNU General Public License as published
46,7 → 46,7
\title{Specification}
\author{Dan Gisselquist, Ph.D.}
\email{dgisselq (at) ieee.org}
\revision{Rev.~0.1}
\revision{Rev.~0.4}
\begin{document}
\pagestyle{gqtekspecplain}
\titlepage
68,6 → 68,7
copy.
\end{license}
\begin{revisionhistory}
0.4 & 6/2/2017 & Gisselquist & Added Compressed scope and TB's\\\hline
0.3 & 6/22/2015 & Gisselquist & Minor updates to enhance readability \\\hline
0.2 & 6/22/2015 & Gisselquist & Finished Draft \\\hline
0.1 & 6/22/2015 & Gisselquist & First Draft \\\hline
90,12 → 91,13
bench tested the components on the hardware itself. Thus, testing and
development continued on the hardware, and the scope helped me see what was
going right or wrong. The great advantage of the approach was that, at the
end of the project, I didn't need to do any hardware in the loop testing.
All of the testing that had been accomplished prior to that date was already
hardware in the loop testing.
end of the project, I didn't need to switch from simulation to hardware in the
loop testing, since all my testing had been done with the hardware in the loop.
 
When I left that job, I took this concept with me and rebuilt this piece of
infrastructure using a Wishbone Bus.
infrastructure using a Wishbone Bus. I am not going to recommend that others
use this approach for bench testing, but I have found it very valuable for
debugging on the hardware.
\end{preface}
 
\chapter{Introduction}
103,7 → 105,9
\setcounter{page}{1}
 
The Wishbone Scope is a debugging tool for reading results from the chip after
events have taken place. In general, the scope records data until some
events have taken place. It designed to be a peripheral on an already
existing wishbone bus--pushing the complicated task of getting a bus up
and running elsewhere. In general, the scope records data until some
some (programmable) holdoff number of data samples after a trigger has taken
place. Once the holdoff has been reached, the scope stops recording and
asserts an interrupt. At this time, data may be read from the scope in order
113,25 → 117,48
dependent. The scope itself is designed to be easily reconfigurable from one
build to the next so that the actual configuration may even be build dependent.
Second, the scope is built to be able to run off of a separate clock from the
bus that commands and controls it. This is configurable, set the parameter
``SYNCHRONOUS'' to `1' to run off of a single clock. When running off of two
clocks, it means that actions associated with commands issued to the scope,
such as manual triggering or being disabled or released, will not act
synchronously with the scope itself--but this is to be expected.
Second, the scope is built to be able to run synchronously with the bus clock,
or off of a separate data clock. Whether or not the two are synchronous is
controlled by the ``SYNCHRONOUS'' parameter. When running off of two
clocks, the actions associated with commands issued to the scope,
such as manual triggering, as well as disabling or releasing the trigger, will
not act synchronously with the scope itself--but this is to be expected.
 
Third, the data clock associated with the scope has a clock enable line
associated with it. Depending on how often the clock enable line is enabled
may determine how fast the scope is {\tt PRIMED}, {\tt TRIGGERED}, and eventually completes
its collection.
may determine how fast the scope is {\tt PRIMED}, {\tt TRIGGERED}, and then
eventually completes its collection.
 
Finally, and in conclusion, this scope has been an invaluable tool for
testing, for figuring out what is going on internal to a chip, and for fixing
such things. I have fixed interactions over a PS/2 connection, Internal
such things. I have diagnosed PS/2 interactions, Internal
Configuration Access Port (ICAPE2) interfaces, mouse controller interactions,
bus errors, quad-SPI flash interactions, and more using this scope.
bus errors, quad-SPI flash interactions, SD--card interface, VGA, HDMI, and
even the internals of a CPU all using this scope.
% \chapter{Architecture}
\chapter{Architecture}
 
The wishbone scope package comes with two separate scopes: the regular scope,
and a run-length encoded scope.
 
Both scopes are designed to be a component of a larger design. They depend upon
the existence of a reliable wishbone bus which can be accessed independent of
the portion of the design under test.
 
Both scopes exist as a slave peripheral on this wishbone bus.
 
The bus master still needs to interact with this slave to first configure it,
and second to read any data off of it.
 
Interaction with the scopes is identical, save for two differences. First, the
run-length encoded scope uses the high order bit to specify the number of
times to repeat the last data item. This means that the run-length encoded
scope can only store 31~bits per time interval, versus the 32~bits per time
interval of the regular scope.
 
Since the two scopes are so similar, they will collectively be called the
Wishbone Scope, and differences will only be mentioned where appropriate.
 
\chapter{Operation}
So how shall one use the scope? The scope itself supports a series of
170,7 → 197,7
active on an input clock with the clock--enable line set, the scope will then
be {\tt TRIGGERED}. It
will then count for the number of clocks in the holdoff before stopping
collection, placing it in the {\tt STOPPED} state. \footnote{You can even
collection, placing it in the {\tt STOPPED} state.\footnote{You can even
change the holdoff while the scope is running by writing a new holdoff value
together with setting the {\tt RESET\_n} bit of the control register. However,
if you do this after the core has triggered it may stop at some other
211,7 → 238,7
\begin{reglist}
CONTROL & 0 & 32 & R/W & Configuration, control, and status of the
scope.\\\hline
DATA & 1 & 32 & R(/W) & Read out register, to read out the data
DATA & 4 & 32 & R(/W) & Read out register, to read out the data
from the core. Writes to this register reset the read address
to the beginning of the buffer, but are otherwise ignored.
\\\hline
253,7 → 280,7
Finally, user's are cautioned not to adjust the holdoff between the time the
scope triggers and the time it stops--just to guarantee data coherency.
 
While this approach works, the scope has some other capabilities. For example,
The scope also has some other capabilities. For example,
if you set the {\tt MANUAL} bit, the scope will trigger as soon as it is {\tt PRIMED}.
If you set the {\tt MANUAL} bit and the {\tt RESET\_n} bit, it will trigger
immediately if the scope was already {\tt PRIMED}. However, if the
281,8 → 308,9
This is perhaps the simplest register to explain. Before the core stops
recording, reads from this register will produce reads of the bits going into
the core, save only that they have not been protected from any meta-stability
issues. This is useful for reading what's going on when the various lines are
stuck. After the core stops recording, reads from this register return values
issues. This may be useful for reading what's going on when the various lines
are stuck, although there are potential race conditions when using this feature.
After the core stops recording, reads from this register return values
from the stored memory, beginning at the oldest and ending with the value
holdoff clocks after the trigger. Further, after recording has stopped, every
read increments an internal memory address, so that after (1$<<$LGMEMLEN)
293,6 → 321,16
be `1' for the first value in the buffer, or you may write to the data register.
Such writes will be ignored, save that they will reset the read address back
to the beginning of the buffer.
 
If the holdoff is set to zero, the last data value will be the value recorded
when the trigger took place. As the holdoff increases, the trigger will move
earlier and earlier into the buffer.
 
The data register for the compressed scope will indicate the presence of a
run in the high order bit. If the high order bit is set, the last value
will be repeated one plus the value held in the register. Hence, a
data value of {\tt 0x80000000} indicates a value repeated once, while
{\tt 0x80000001 } indicates the value has been repeated twice and so on.
\chapter{Clocks}
 
302,6 → 340,9
will save some flip flops and logic in implementation. The speeds of the
respective clocks are based upon the speed of your device, and not specific
to this core.
 
That said, I have run the core up to 200~MHz on a Xilinx Artix-7, and so
it has been modified to match that speed.
\chapter{Wishbone Datasheet}\label{chap:wishbone}
Tbl.~\ref{tbl:wishbone}
337,28 → 378,37
to note that the scope supports pipeline reads from the data port, to speed
up reading the results out.
 
What this table doesn't show is that all accesses to the port take a single
clock. That is, if the {\tt i\_wb\_stb} line is high on one clock, the
{\tt i\_wb\_ack} line will be high the next. Further, the {\tt o\_wb\_stall}
line is tied to zero.
The {\tt o\_wb\_stall} line is tied to zero.
 
The {\tt i\_wb\_cyc} line is assumed any time {\tt i\_wb\_stb} is high, and so
the core ignores {\tt i\_wb\_cyc}.
 
The core does not implement the {\tt i\_wb\_sel} lines. Writes to the core
of values less than a word are undefined. Reads of less than a word in
size will act as whole word reads.
 
 
\chapter{I/O Ports}\label{ch:ioports}
 
The ports are listed in Table.~\ref{tbl:ioports}.
The external I/O ports for both cores are listed in Table.~\ref{tbl:ioports}.
\begin{table}[htbp]
\begin{center}
\begin{portlist}
{\tt i\_clk} & 1 & Input & The clock the data lines, clock enable, and trigger
are synchronous to. \\\hline
{\tt i\_data\_clk} & 1 & Input & The clock the data lines, clock enable, and
trigger are synchronous to. \\\hline
{\tt i\_ce} & 1 & Input & Clock Enable. Set this high to clock data in and
out.\\\hline
out. No data will move through the core if this is low. \\\hline
{\tt i\_trigger} & 1 & Input & An active high trigger line. If this trigger is
set to one on any clock enabled data clock cycle, once
the scope has been {\tt PRIMED}, it will then enter into its
{\tt TRIGGERED} state.
\\\hline
{\tt i\_data} & 32 & Input & 32--wires of ... whatever you are interested in
recording and later examining. These can be anything, only
they should be synchronous with the data clock.
{\tt i\_data} & 32 & Input & \parbox{3.3in}{{\tt WBSCOPE ONLY: } 32--wires of
... whatever you
are interested in recording and later examining. These can be anything,
only they should be synchronous with the data clock.
 
{\tt WBSCOPC: } The data width is only 31 wide instead of 32}
\\\hline
{\tt i\_wb\_clk} & 1 & Input & The clock that the wishbone interface runs on.
\\\hline
375,7 → 425,7
{\tt i\_wb\_data} & 32 & Input & Data used when writing to the control register,
ignored otherwise. \\\hline
{\tt o\_wb\_ack} & 1 & Output & Wishbone acknowledgement. This line will go
high on the clock after any wishbone access, as long as the
high two clocks after any wishbone access, as long as the
wishbone {\tt i\_wb\_cyc} line remains high (i.e., no ack's if
you terminate the cycle early).
\\\hline
/trunk/rtl/Makefile
0,0 → 1,69
################################################################################
##
## Filename: rtl/Makefile
##
## Project: WBScope, a wishbone hosted scope
##
## Purpose: To direct the Verilator build of the SoC sources. The result
## is C++ code (built by Verilator), that is then built (herein)
## into a library.
##
## Targets: The default target, all, builds the target test, which includes
## the libraries necessary for Verilator testing.
##
## Creator: Dan Gisselquist, Ph.D.
## Gisselquist Technology, LLC
##
################################################################################
##
## Copyright (C) 2015-2017, Gisselquist Technology, LLC
##
## This program is free software (firmware): you can redistribute it and/or
## modify it under the terms of the GNU General Public License as published
## by the Free Software Foundation, either version 3 of the License, or (at
## your option) any later version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 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 program. (It's in the $(ROOT)/doc directory. Run make with no
## target there if the PDF file isn't present.) If not, see
## <http://www.gnu.org/licenses/> for a copy.
##
## License: GPL, v3, as defined and found on www.gnu.org,
## http://www.gnu.org/licenses/gpl.html
##
################################################################################
##
##
all: test axi
FBDIR := .
VDIRFB:= $(FBDIR)/obj_dir
 
.PHONY: test
test: $(VDIRFB)/Vwbscope__ALL.a
test: $(VDIRFB)/Vwbscopc__ALL.a
.PHONY: axi
axi: $(VDIRFB)/Vaxi4lscope__ALL.a
 
$(VDIRFB)/Vwbscope__ALL.a: $(VDIRFB)/Vwbscope.h $(VDIRFB)/Vwbscope.cpp
$(VDIRFB)/Vwbscope__ALL.a: $(VDIRFB)/Vwbscope.mk
$(VDIRFB)/Vwbscope.h $(VDIRFB)/Vwbscope.cpp $(VDIRFB)/Vwbscope.mk: wbscope.v
 
$(VDIRFB)/Vwbscopc__ALL.a: $(VDIRFB)/Vwbscopc.h $(VDIRFB)/Vwbscopc.cpp
$(VDIRFB)/Vwbscopc__ALL.a: $(VDIRFB)/Vwbscopc.mk
$(VDIRFB)/Vwbscopc.h $(VDIRFB)/Vwbscopc.cpp $(VDIRFB)/Vwbscopc.mk: wbscopc.v
 
$(VDIRFB)/V%.cpp $(VDIRFB)/V%.h $(VDIRFB)/V%.mk: $(FBDIR)/%.v
verilator -Wall -cc $*.v
 
$(VDIRFB)/V%__ALL.a: $(VDIRFB)/V%.mk
cd $(VDIRFB); make -f V$*.mk
 
.PHONY: clean
clean:
rm -rf $(VDIRFB)/
 
/trunk/rtl/axi4lscope.v
3,7 → 3,7
//
// Filename: axi4lscope.v
//
// Project: FPGA Library of Routines
// Project: WBScope, a wishbone hosted scope
//
// Purpose: This is a generic/library routine for providing a bus accessed
// 'scope' or (perhaps more appropriately) a bus accessed logic analyzer.
12,7 → 12,7
// reset, the scope records a copy of the input data every time the clock
// ticks with the circuit enabled. That is, it records these values up
// until the trigger. Once the trigger goes high, the scope will record
// for bw_holdoff more counts before stopping. Values may then be read
// for br_holdoff more counts before stopping. Values may then be read
// from the buffer, oldest to most recent. After reading, the scope may
// then be reset for another run.
//
28,7 → 28,7
// 5. The scope recording is then paused until the next reset.
// 6. While stopped, the CPU can read the data from the scope
// 7. -- oldest to most recent
// 8. -- one value per i_rd&i_clk
// 8. -- one value per i_rd&i_data_clk
// 9. Writes to the data register reset the address to the
// beginning of the buffer
//
68,7 → 68,7
//
////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2015-2016, Gisselquist Technology, LLC
// Copyright (C) 2015-2017, Gisselquist Technology, LLC
//
// This program is free software (firmware): you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
81,7 → 81,7
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. (It's in the $(ROOT)/doc directory, run make with no
// with this program. (It's in the $(ROOT)/doc directory. Run make with no
// target there if the PDF file isn't present.) If not, see
// <http://www.gnu.org/licenses/> for a copy.
//
92,13 → 92,17
////////////////////////////////////////////////////////////////////////////////
//
//
`default_nettype none
//
module axi4lscope
#(
// Users to add parameters here
parameter LGMEM = 5'd10,
parameter [4:0] LGMEM = 5'd10,
parameter BUSW = 32,
parameter SYNCHRONOUS=1,
parameter DEFAULT_HOLDOFF = ((1<<(LGMEM-1))-4),
parameter HOLDOFFBITS = 20,
parameter [(HOLDOFFBITS-1):0] DEFAULT_HOLDOFF
= ((1<<(LGMEM-1))-4),
// User parameters ends
// DO NOT EDIT BELOW THIS LINE ---------------------
// Do not modify the parameters beyond this line
109,7 → 113,7
)
(
// Users to add ports here
input wire i_clk, // The data clock, can be set to ACLK
input wire i_data_clk, // The data clock, can be set to ACLK
input wire i_ce, // = '1' when recordable data is present
input wire i_trigger,// = '1' when interesting event hapns
input wire [31:0] i_data,
188,11 → 192,12
reg axi_bvalid;
reg [C_S_AXI_ADDR_WIDTH-1 : 0] axi_araddr;
reg axi_arready;
reg [C_S_AXI_DATA_WIDTH-1 : 0] axi_rdata;
// reg [1 : 0] axi_rresp;
reg axi_rvalid;
 
 
wire write_stb;
 
///////////////////////////////////////////////////
//
// Decode and handle the AXI/Bus signaling
234,8 → 239,6
axi_wready <= 1'b0;
assign S_AXI_WREADY = axi_wready;
 
wire write_stb;
 
always @(posedge S_AXI_ACLK)
if (i_reset)
begin
303,6 → 306,9
// From here on down, Gisselquist Technology, LLC,
// claims a copyright on the code.
//
wire bus_clock;
assign bus_clock = S_AXI_ACLK;
 
wire read_from_data;
assign read_from_data = (S_AXI_ARVALID)&&(S_AXI_ARREADY)
&&(axi_araddr[0]);
312,6 → 318,9
wire write_to_control;
assign write_to_control = (write_stb)&&(!axi_awaddr[0]);
 
reg read_address;
always @(posedge bus_clock)
read_address <= axi_araddr[0];
 
wire [31:0] i_wb_data;
assign i_wb_data = S_AXI_WDATA;
338,22 → 347,22
// Our status/config register
wire bw_reset_request, bw_manual_trigger,
bw_disable_trigger, bw_reset_complete;
reg [22:0] br_config;
wire [19:0] bw_holdoff;
initial br_config = DEFAULT_HOLDOFF;
always @(posedge S_AXI_ACLK)
reg [2:0] br_config;
reg [(HOLDOFFBITS-1):0] br_holdoff;
initial br_config = 3'b0;
initial br_holdoff = DEFAULT_HOLDOFF;
always @(posedge bus_clock)
if (write_to_control)
begin
br_config <= { i_wb_data[31],
(i_wb_data[27]),
i_wb_data[26],
i_wb_data[19:0] };
i_wb_data[27],
i_wb_data[26] };
br_holdoff <= i_wb_data[(HOLDOFFBITS-1):0];
end else if (bw_reset_complete)
br_config[22] <= 1'b1;
assign bw_reset_request = (~br_config[22]);
assign bw_manual_trigger = (br_config[21]);
assign bw_disable_trigger = (br_config[20]);
assign bw_holdoff = br_config[19:0];
br_config[2] <= 1'b1;
assign bw_reset_request = (!br_config[2]);
assign bw_manual_trigger = (br_config[1]);
assign bw_disable_trigger = (br_config[0]);
 
wire dw_reset, dw_manual_trigger, dw_disable_trigger;
generate
372,7 → 381,7
// so do a clock transfer here
initial q_iflags = 3'b000;
initial r_reset_complete = 1'b0;
always @(posedge i_clk)
always @(posedge i_data_clk)
begin
q_iflags <= { bw_reset_request, bw_manual_trigger, bw_disable_trigger };
r_iflags <= q_iflags;
389,7 → 398,7
// clock that the reset has been accomplished
initial q_reset_complete = 1'b0;
initial qq_reset_complete = 1'b0;
always @(posedge S_AXI_ACLK)
always @(posedge bus_clock)
begin
q_reset_complete <= r_reset_complete;
qq_reset_complete <= q_reset_complete;
403,15 → 412,14
//
//
// Write with the i-clk, or input clock. All outputs read with the
// WISHBONE-clk, or S_AXI_ACLK clock.
// bus clock, or bus_clock as we've called it here.
reg dr_triggered, dr_primed;
wire dw_trigger;
assign dw_trigger = (dr_primed)&&(
((i_trigger)&&(~dw_disable_trigger))
||(dr_triggered)
((i_trigger)&&(!dw_disable_trigger))
||(dw_manual_trigger));
initial dr_triggered = 1'b0;
always @(posedge i_clk)
always @(posedge i_data_clk)
if (dw_reset)
dr_triggered <= 1'b0;
else if ((i_ce)&&(dw_trigger))
421,24 → 429,26
// Determine when memory is full and capture is complete
//
// Writes take place on the data clock
// The counter is unsigned
(* ASYNC_REG="TRUE" *) reg [(HOLDOFFBITS-1):0] counter;
 
reg dr_stopped;
reg [19:0] counter; // This is unsigned
initial dr_stopped = 1'b0;
initial counter = 20'h0000;
always @(posedge i_clk)
initial counter = 0;
always @(posedge i_data_clk)
if (dw_reset)
counter <= 0;
else if ((i_ce)&&(dr_triggered)&&(!dr_stopped))
begin
counter <= 0;
counter <= counter + 1'b1;
end
always @(posedge i_data_clk)
if ((!dr_triggered)||(dw_reset))
dr_stopped <= 1'b0;
end else if ((i_ce)&&(dr_triggered))
begin // MUST BE a < and not <=, so that we can keep this w/in
// 20 bits. Else we'd need to add a bit to comparison
// here.
if (counter < bw_holdoff)
counter <= counter + 20'h01;
else
dr_stopped <= 1'b1;
end
else if (HOLDOFFBITS > 1) // if (i_ce)
dr_stopped <= (counter >= br_holdoff);
else if (HOLDOFFBITS <= 1)
dr_stopped <= ((i_ce)&&(dw_trigger));
 
//
// Actually do our writes to memory. Record, via 'primed' when
453,21 → 463,58
reg [(LGMEM-1):0] waddr;
initial waddr = {(LGMEM){1'b0}};
initial dr_primed = 1'b0;
always @(posedge i_clk)
always @(posedge i_data_clk)
if (dw_reset) // For simulation purposes, supply a valid value
begin
waddr <= 0; // upon reset.
dr_primed <= 1'b0;
end else if ((i_ce)&&((~dr_triggered)||(counter < bw_holdoff)))
end else if ((i_ce)&&(!dr_stopped))
begin
// mem[waddr] <= i_data;
waddr <= waddr + {{(LGMEM-1){1'b0}},1'b1};
dr_primed <= (dr_primed)||(&waddr);
if (!dr_primed)
begin
//if (br_holdoff[(HOLDOFFBITS-1):LGMEM]==0)
// dr_primed <= (waddr >= br_holdoff[(LGMEM-1):0]);
// else
dr_primed <= (&waddr);
end
end
always @(posedge i_clk)
if ((i_ce)&&((~dr_triggered)||(counter < bw_holdoff)))
mem[waddr] <= i_data;
 
// Delay the incoming data so that we can get our trigger
// logic to line up with the data. The goal is to have a
// hold off of zero place the trigger in the last memory
// address.
localparam STOPDELAY = 1;
wire [(BUSW-1):0] wr_piped_data;
generate
if (STOPDELAY == 0)
// No delay ... just assign the wires to our input lines
assign wr_piped_data = i_data;
else if (STOPDELAY == 1)
begin
//
// Delay by one means just register this once
reg [(BUSW-1):0] data_pipe;
always @(posedge i_data_clk)
if (i_ce)
data_pipe <= i_data;
assign wr_piped_data = data_pipe;
end else begin
// Arbitrary delay ... use a longer pipe
reg [(STOPDELAY*BUSW-1):0] data_pipe;
 
always @(posedge i_data_clk)
if (i_ce)
data_pipe <= { data_pipe[((STOPDELAY-1)*BUSW-1):0], i_data };
assign wr_piped_data = { data_pipe[(STOPDELAY*BUSW-1):((STOPDELAY-1)*BUSW)] };
end endgenerate
 
always @(posedge i_data_clk)
if ((i_ce)&&(!dr_stopped))
mem[waddr] <= wr_piped_data;
 
//
// Clock transfer of the status signals
//
488,7 → 535,7
reg [2:0] r_oflags;
initial q_oflags = 3'h0;
initial r_oflags = 3'h0;
always @(posedge S_AXI_ACLK)
always @(posedge bus_clock)
if (bw_reset_request)
begin
q_oflags <= 3'h0;
504,34 → 551,37
end endgenerate
 
// Reads use the bus clock
reg br_wb_ack;
initial br_wb_ack = 1'b0;
always @(posedge S_AXI_ACLK)
always @(posedge bus_clock)
begin
if ((bw_reset_request)||((write_stb)&&(axi_awaddr[0])))
if ((bw_reset_request)||(write_to_control))
raddr <= 0;
else if ((read_from_data)&&(bw_stopped))
// Data read ... only takes place when stopped
raddr <= raddr + {{(LGMEM-1){1'b0}},1'b1};
raddr <= raddr + 1'b1; // Data read, when stopped
end
 
reg [(LGMEM-1):0] this_addr;
always @(posedge bus_clock)
if (read_from_data)
this_addr <= raddr + waddr + 1'b1;
else
this_addr <= raddr + waddr;
 
reg [31:0] nxt_mem;
always @(posedge S_AXI_ACLK)
nxt_mem <= mem[raddr+waddr+ ((read_from_data) ?
{{(LGMEM-1){1'b0}},1'b1} : { (LGMEM){1'b0}} )];
always @(posedge bus_clock)
nxt_mem <= mem[this_addr];
 
wire [19:0] full_holdoff;
assign full_holdoff[(HOLDOFFBITS-1):0] = br_holdoff;
generate if (HOLDOFFBITS < 20)
assign full_holdoff[19:(HOLDOFFBITS)] = 0;
endgenerate
 
 
 
 
 
 
reg [31:0] o_bus_data;
wire [4:0] bw_lgmem;
assign bw_lgmem = LGMEM;
always @(posedge S_AXI_ACLK)
if (~axi_araddr[0]) // Control register read
axi_rdata <= { bw_reset_request,
always @(posedge bus_clock)
if (!read_address) // Control register read
o_bus_data <= { bw_reset_request,
bw_stopped,
bw_triggered,
bw_primed,
539,24 → 589,29
bw_disable_trigger,
(raddr == {(LGMEM){1'b0}}),
bw_lgmem,
bw_holdoff };
else if (~bw_stopped) // read, prior to stopping
axi_rdata <= i_data;
full_holdoff };
else if (!bw_stopped) // read, prior to stopping
o_bus_data <= i_data;
else // if (i_wb_addr) // Read from FIFO memory
axi_rdata <= nxt_mem; // mem[raddr+waddr];
assign S_AXI_RDATA = axi_rdata;
o_bus_data <= nxt_mem; // mem[raddr+waddr];
 
assign S_AXI_RDATA = o_bus_data;
 
reg br_level_interrupt;
initial br_level_interrupt = 1'b0;
assign o_interrupt = (bw_stopped)&&(~bw_disable_trigger)
&&(~br_level_interrupt);
always @(posedge S_AXI_ACLK)
assign o_interrupt = (bw_stopped)&&(!bw_disable_trigger)
&&(!br_level_interrupt);
always @(posedge bus_clock)
if ((bw_reset_complete)||(bw_reset_request))
br_level_interrupt<= 1'b0;
else
br_level_interrupt<= (bw_stopped)&&(~bw_disable_trigger);
br_level_interrupt<= (bw_stopped)&&(!bw_disable_trigger);
 
// verilator lint_off UNUSED
// Make verilator happy
wire [44:0] unused;
assign unused = { S_AXI_WSTRB, S_AXI_ARPROT, S_AXI_AWPROT,
axi_awaddr[3:1], axi_araddr[3:1],
i_wb_data[30:28], i_wb_data[25:0] };
// verilator lint_on UNUSED
endmodule
 
 
/trunk/rtl/wbscopc.v
5,12 → 5,10
// Project: WBScope, a wishbone hosted scope
//
// Purpose: This scope is identical in function to the wishbone scope
// found in wbscope, save that the output is compressed and that (as a
// result) it can only handle recording 31 bits at a time. This allows
// the top bit to indicate an 'address difference'. Okay, there's
// another difference as well: this version only works in a synchronous
// fashion with the clock from the WB bus. You cannot have a separate
// bus and data clock.
// found in wbscope, save that the output is compressed via a run-length
// encoding scheme and that (as a result) it can only handle recording
// 31 bits at a time. This allows the top bit to indicate the presence
// of an 'address difference' rather than actual incoming recorded data.
//
// Reading/decompressing the output of this scope works in this fashion:
// Once the scope has stopped, read from the port. Any time the high
18,46 → 16,28
// the last value. If the high order bit is not set, then the value
// is a new data value.
//
// I've provided this version of a compressed scope to OpenCores for
// discussion purposes. While wbscope.v works and works well by itself,
// this compressed scope has a couple of fundamental flaw that I have
// yet to fix. One of them is that it is impossible to know when the
// trigger took place. The second problem is that it may be impossible
// to know the state of the scope at the beginning of the buffer--should
// the buffer begin with an address difference value instead of a data
// value.
// Previous versions of the compressed scope have had some fundamental
// flaws: 1) it was impossible to know when the trigger took place, and
// 2) if things never changed, the scope would never fill or complete
// its capture. These two flaws have been fixed with this release.
//
// Ideally, the first item read out of the scope should be a data value,
// even if the scope was skipping values to a new address at the time.
// If it was in the middle of a skip, the next item out of the scope
// should be the skip length. This, though, violates the rule that there
// are (1<<LGMEMLEN) items in the memory, and that the trigger took place
// on the last item of memory ... so that portion of this compressed
// scope is still to be defined.
// When dealing with a slow interface such as the PS/2 interface, or even
// the 16x2 LCD interface, it is often true that things can change _very_
// slowly. They could change so slowly that the standard wishbone scope
// doesn't work. This scope then gives you a working scope, by sampling
// at diverse intervals, and only capturing anything that changes within
// those intervals.
//
// Like I said, this version is placed here for discussion purposes,
// not because it runs well nor because I have recognized that it has any
// particular value (yet).
// Indeed, I'm finding this compressed scope very valuable for evaluating
// the timing associated with a GPS PPS and associated NMEA stream. I
// need to collect over a seconds worth of data, and I don't have enough
// memory to handle one memory value per clock, yet I still want to know
// exactly when the GPS PPS goes high, when it goes low, when I'm
// adjusting my clock, and when the clock's PPS output goes high. Did I
// synchronize them well? Oh, and when does the NMEA time string show up
// when compared with the PPS? All of those are valuable, but could never
// be done if the scope wasn't compressed.
//
// Well, I take that back. When dealing with an interface such as the
// PS/2 interface, or even the 16x2 LCD interface, it is often true
// that things change _very_ slowly. They could change so slowly that
// the other approach to the scope doesn't work. This then gives you
// a working scope, by only capturing the changes. You'll still need
// to figure out (after the fact) when the trigge took place. Perhaps
// you'll wish to add the trigger as another data line, so you can find
// when it took place in your own data?
//
// Okay, I take that back twice: I'm finding this compressed scope very
// valuable for evaluating the timing associated with a GPS PPS and
// associated NMEA stream. I need to collect over a seconds worth of
// data, and I don't have enough memory to handle one memory value per
// clock, yet I still want to know exactly when the GPS PPS goes high,
// when it goes low, when I'm adjusting my clock, and when the clock's
// PPS output goes high. Did I synchronize them well? Oh, and when does
// the NMEA time string show up when compared with the PPS? All of those
// are valuable, but could never be done if the scope wasn't compressed.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
87,20 → 67,28
////////////////////////////////////////////////////////////////////////////////
//
//
module wbscopc(i_clk, i_ce, i_trigger, i_data,
`default_nettype none
//
module wbscopc(i_data_clk, i_ce, i_trigger, i_data,
i_wb_clk, i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data,
o_wb_ack, o_wb_stall, o_wb_data,
o_interrupt);
parameter LGMEM = 5'd10, NELM=31, BUSW = 32, SYNCHRONOUS=1;
parameter [4:0] LGMEM = 5'd10;
parameter BUSW = 32, NELM=(BUSW-1);
parameter [0:0] SYNCHRONOUS=1;
parameter HOLDOFFBITS=20;
parameter [(HOLDOFFBITS-1):0] DEFAULT_HOLDOFF = ((1<<(LGMEM-1))-4);
parameter STEP_BITS=BUSW-1;
parameter [(STEP_BITS-1):0] MAX_STEP= {(STEP_BITS){1'b1}};
// The input signals that we wish to record
input i_clk, i_ce, i_trigger;
input [(NELM-1):0] i_data;
input wire i_data_clk, i_ce, i_trigger;
input wire [(NELM-1):0] i_data;
// The WISHBONE bus for reading and configuring this scope
input i_wb_clk, i_wb_cyc, i_wb_stb, i_wb_we;
input i_wb_addr; // One address line only
input [(BUSW-1):0] i_wb_data;
input wire i_wb_clk, i_wb_cyc, i_wb_stb, i_wb_we;
input wire i_wb_addr; // One address line only
input wire [(BUSW-1):0] i_wb_data;
output wire o_wb_ack, o_wb_stall;
output wire [(BUSW-1):0] o_wb_data;
output reg [(BUSW-1):0] o_wb_data;
// And, finally, for a final flair --- offer to interrupt the CPU after
// our trigger has gone off. This line is equivalent to the scope
// being stopped. It is not maskable here.
107,47 → 95,216
output wire o_interrupt;
 
 
// Let's first see how far we can get by cheating. We'll use the
// wbscope program, and suffer a lack of several features
 
// When is the full scope reset? Capture that reset bit from any
// write.
wire lcl_reset;
assign lcl_reset = (i_wb_stb)&&(~i_wb_addr)&&(i_wb_we)
&&(~i_wb_data[31]);
reg [(LGMEM-1):0] raddr;
reg [(BUSW-1):0] mem[0:((1<<LGMEM)-1)];
 
// A big part of this scope is the 'address' of any particular
// data value. As of this current version, the 'address' changed
// in definition from an absolute time (which had all kinds of
// problems) to a difference in time. Hence, when the address line
// is high on decompression, the 'address' field will record an
// Our status/config register
wire bw_reset_request, bw_manual_trigger,
bw_disable_trigger, bw_reset_complete;
reg [2:0] br_config;
reg [(HOLDOFFBITS-1):0] br_holdoff;
initial br_config = 3'b0;
initial br_holdoff = DEFAULT_HOLDOFF;
always @(posedge i_wb_clk)
if ((i_wb_stb)&&(!i_wb_addr))
begin
if (i_wb_we)
begin
br_config <= { i_wb_data[31],
i_wb_data[27],
i_wb_data[26] };
br_holdoff <= i_wb_data[(HOLDOFFBITS-1):0];
end
end else if (bw_reset_complete)
br_config[2] <= 1'b1;
assign bw_reset_request = (!br_config[2]);
assign bw_manual_trigger = (br_config[1]);
assign bw_disable_trigger = (br_config[0]);
 
 
wire dw_reset, dw_manual_trigger, dw_disable_trigger;
generate
if (SYNCHRONOUS > 0)
begin
assign dw_reset = bw_reset_request;
assign dw_manual_trigger = bw_manual_trigger;
assign dw_disable_trigger = bw_disable_trigger;
assign bw_reset_complete = bw_reset_request;
end else begin
reg r_reset_complete;
(* ASYNC_REG = "TRUE" *) reg [2:0] q_iflags;
reg [2:0] r_iflags;
 
// Resets are synchronous to the bus clock, not the data clock
// so do a clock transfer here
initial q_iflags = 3'b000;
initial r_reset_complete = 1'b0;
always @(posedge i_data_clk)
begin
q_iflags <= { bw_reset_request, bw_manual_trigger, bw_disable_trigger };
r_iflags <= q_iflags;
r_reset_complete <= (dw_reset);
end
 
assign dw_reset = r_iflags[2];
assign dw_manual_trigger = r_iflags[1];
assign dw_disable_trigger = r_iflags[0];
 
(* ASYNC_REG = "TRUE" *) reg q_reset_complete;
reg qq_reset_complete;
// Pass an acknowledgement back from the data clock to the bus
// clock that the reset has been accomplished
initial q_reset_complete = 1'b0;
initial qq_reset_complete = 1'b0;
always @(posedge i_wb_clk)
begin
q_reset_complete <= r_reset_complete;
qq_reset_complete <= q_reset_complete;
end
 
assign bw_reset_complete = qq_reset_complete;
end endgenerate
 
//
// Set up the trigger
//
//
// Write with the i-clk, or input clock. All outputs read with the
// WISHBONE-clk, or i_wb_clk clock.
reg dr_triggered, dr_primed;
wire dw_trigger;
assign dw_trigger = (dr_primed)&&(
((i_trigger)&&(!dw_disable_trigger))
||(dw_manual_trigger));
initial dr_triggered = 1'b0;
always @(posedge i_data_clk)
if (dw_reset)
dr_triggered <= 1'b0;
else if ((i_ce)&&(dw_trigger))
dr_triggered <= 1'b1;
 
//
// Determine when memory is full and capture is complete
//
// Writes take place on the data clock
// The counter is unsigned
(* ASYNC_REG="TRUE" *) reg [(HOLDOFFBITS-1):0] holdoff_counter;
 
reg dr_stopped;
initial dr_stopped = 1'b0;
initial holdoff_counter = 0;
always @(posedge i_data_clk)
if (dw_reset)
holdoff_counter <= 0;
else if ((i_ce)&&(dr_triggered)&&(!dr_stopped))
begin
holdoff_counter <= holdoff_counter + 1'b1;
end
 
always @(posedge i_data_clk)
if ((!dr_triggered)||(dw_reset))
dr_stopped <= 1'b0;
else if ((i_ce)&&(!dr_stopped))
begin
if (HOLDOFFBITS > 1) // if (i_ce)
dr_stopped <= (holdoff_counter >= br_holdoff);
else if (HOLDOFFBITS <= 1)
dr_stopped <= ((i_ce)&&(dw_trigger));
end
 
localparam DLYSTOP=5;
reg [(DLYSTOP-1):0] dr_stop_pipe;
always @(posedge i_data_clk)
if (dw_reset)
dr_stop_pipe <= 0;
else if (i_ce)
dr_stop_pipe <= { dr_stop_pipe[(DLYSTOP-2):0], dr_stopped };
 
wire dw_final_stop;
assign dw_final_stop = dr_stop_pipe[(DLYSTOP-1)];
 
// A big part of this scope is the run length of any particular
// data value. Hence, when the address line (i.e. data[31])
// is high on decompression, the run length field will record an
// address difference.
//
// To implement this, we set our 'address' to zero any time the
// To implement this, we set our run length to zero any time the
// data changes, but increment it on all other clocks. Should the
// address difference get to our maximum value, we let it saturate
// rather than overflow.
reg [(BUSW-2):0] ck_addr;
reg [(NELM-1):0] lst_dat;
reg [(STEP_BITS-1):0] ck_addr;
reg [(NELM-1):0] qd_data;
reg dr_force_write, dr_run_timeout,
new_data;
 
//
// The "dr_force_write" logic here is designed to make sure we write
// at least every MAX_STEP samples, and that we stop as soon as
// we are able. Hence, if an interface is slow
// and idle, we'll at least prime the scope, and even if the interface
// doesn't have enough transitions to fill our buffer, we'll at least
// fill the buffer with repeats.
//
reg dr_force_inhibit;
initial ck_addr = 0;
always @(posedge i_clk)
if ((lcl_reset)||((i_ce)&&(i_data != lst_dat)))
initial dr_force_write = 1'b0;
always @(posedge i_data_clk)
if (dw_reset)
begin
dr_force_write <= 1'b1;
dr_force_inhibit <= 1'b0;
end else if (i_ce)
begin
dr_force_inhibit <= (dr_force_write);
if ((dr_run_timeout)&&(!dr_force_write)&&(!dr_force_inhibit))
dr_force_write <= 1'b1;
else if (((dw_trigger)&&(!dr_triggered))||(!dr_primed))
dr_force_write <= 1'b1;
else
dr_force_write <= 1'b0;
end
 
//
// Keep track of how long it has been since the last write
//
always @(posedge i_data_clk)
if (dw_reset)
ck_addr <= 0;
else if (&ck_addr)
; // Saturated (non-overflowing) address diff
else
ck_addr <= ck_addr + 1;
else if (i_ce)
begin
if ((dr_force_write)||(new_data)||(dr_stopped))
ck_addr <= 0;
else
ck_addr <= ck_addr + 1'b1;
end
 
always @(posedge i_data_clk)
if (dw_reset)
dr_run_timeout <= 1'b1;
else if (i_ce)
dr_run_timeout <= (ck_addr >= MAX_STEP-1'b1);
 
always @(posedge i_data_clk)
if (dw_reset)
new_data <= 1'b1;
else if (i_ce)
new_data <= (i_data != qd_data);
 
always @(posedge i_data_clk)
if (i_ce)
qd_data <= i_data;
 
wire [(BUSW-2):0] w_data;
generate
if (NELM == BUSW-1)
assign w_data = i_data;
assign w_data = qd_data;
else
assign w_data = { {(BUSW-NELM-1){1'b0}}, i_data };
assign w_data = { {(BUSW-NELM-1){1'b0}}, qd_data };
endgenerate
 
//
// To do our compression, we keep track of two registers: the most
// To do our RLE compression, we keep track of two registers: the most
// recent data to the device (imm_ prefix) and the data from one
// clock ago. This allows us to suppress writes to the scope which
// would otherwise be two address writes in a row.
154,29 → 311,29
reg imm_adr, lst_adr; // Is this an address (1'b1) or data value?
reg [(BUSW-2):0] lst_val, // Data for the scope, delayed by one
imm_val; // Data to write to the scope
initial lst_dat = 0;
initial lst_adr = 1'b1;
initial imm_adr = 1'b1;
always @(posedge i_clk)
if (lcl_reset)
always @(posedge i_data_clk)
if (dw_reset)
begin
imm_val <= 31'h0;
imm_adr <= 1'b1;
lst_val <= 31'h0;
lst_adr <= 1'b1;
lst_dat <= 0;
end else if ((i_ce)&&(i_data != lst_dat))
end else if (i_ce)
begin
imm_val <= w_data;
imm_adr <= 1'b0;
lst_val <= imm_val;
lst_adr <= imm_adr;
lst_dat <= i_data;
end else begin
imm_val <= ck_addr; // Minimum value here is '1'
imm_adr <= 1'b1;
lst_val <= imm_val;
lst_adr <= imm_adr;
if ((new_data)||(dr_force_write)||(dr_stopped))
begin
imm_val <= w_data;
imm_adr <= 1'b0; // Last thing we wrote was data
lst_val <= imm_val;
lst_adr <= imm_adr;
end else begin
imm_val <= ck_addr; // Minimum value here is '1'
imm_adr <= 1'b1; // This (imm) is an address
lst_val <= imm_val;
lst_adr <= imm_adr;
end
end
 
//
183,38 → 340,163
// Here's where we suppress writing pairs of address words to the
// scope at once.
//
reg r_ce;
reg record_ce;
reg [(BUSW-1):0] r_data;
initial r_ce = 1'b0;
always @(posedge i_clk)
r_ce <= (~lst_adr)||(~imm_adr);
always @(posedge i_clk)
r_data <= ((~lst_adr)||(~imm_adr))
initial record_ce = 1'b0;
always @(posedge i_data_clk)
record_ce <= (i_ce)&&((!lst_adr)||(!imm_adr))&&(!dr_stop_pipe[2]);
always @(posedge i_data_clk)
r_data <= ((!lst_adr)||(!imm_adr))
? { lst_adr, lst_val }
: { {(32 - NELM){1'b0}}, i_data };
: { {(32 - NELM){1'b0}}, qd_data };
 
//
// Actually do our writes to memory. Record, via 'primed' when
// the memory is full.
//
// The 'waddr' address that we are using really crosses two clock
// domains. While writing and changing, it's in the data clock
// domain. Once stopped, it becomes part of the bus clock domain.
// The clock transfer on the stopped line handles the clock
// transfer for these signals.
//
reg [(LGMEM-1):0] waddr;
initial waddr = {(LGMEM){1'b0}};
initial dr_primed = 1'b0;
always @(posedge i_data_clk)
if (dw_reset) // For simulation purposes, supply a valid value
begin
waddr <= 0; // upon reset.
dr_primed <= 1'b0;
end else if (record_ce)
begin
// mem[waddr] <= i_data;
waddr <= waddr + {{(LGMEM-1){1'b0}},1'b1};
dr_primed <= (dr_primed)||(&waddr);
end
always @(posedge i_data_clk)
if (record_ce)
mem[waddr] <= r_data;
 
 
//
// The trigger needs some extra attention, in order to keep triggers
// that happen between events from being ignored.
//
wire w_trigger;
assign w_trigger = (r_trigger)||(i_trigger);
//
// Bus response
//
//
 
reg r_trigger;
initial r_trigger = 1'b0;
always @(posedge i_clk)
if (lcl_reset)
r_trigger <= 1'b0;
else
r_trigger <= w_trigger;
//
// Clock transfer of the status signals
//
wire bw_stopped, bw_triggered, bw_primed;
generate
if (SYNCHRONOUS > 0)
begin
assign bw_stopped = dw_final_stop;
assign bw_triggered = dr_triggered;
assign bw_primed = dr_primed;
end else begin
// These aren't a problem, since none of these are strobe
// signals. They goes from low to high, and then stays high
// for many clocks. Swapping is thus easy--two flip flops to
// protect against meta-stability and we're done.
//
(* ASYNC_REG = "TRUE" *) reg [2:0] q_oflags;
reg [2:0] r_oflags;
initial q_oflags = 3'h0;
initial r_oflags = 3'h0;
always @(posedge i_wb_clk)
if (bw_reset_request)
begin
q_oflags <= 3'h0;
r_oflags <= 3'h0;
end else begin
q_oflags <= { dw_final_stop, dr_triggered, dr_primed };
r_oflags <= q_oflags;
end
 
assign bw_stopped = r_oflags[2];
assign bw_triggered = r_oflags[1];
assign bw_primed = r_oflags[0];
end endgenerate
 
 
//
// Call the regular wishbone scope to do all of our real work, now
// that we've compressed the input.
// Reads use the bus clock
//
wbscope #(.SYNCHRONOUS(1), .LGMEM(LGMEM),
.BUSW(BUSW)) cheatersscope(i_clk, r_ce, w_trigger, r_data,
i_wb_clk, i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data,
o_wb_ack, o_wb_stall, o_wb_data, o_interrupt);
reg br_wb_ack, br_pre_wb_ack;
initial br_wb_ack = 1'b0;
wire bw_cyc_stb;
assign bw_cyc_stb = (i_wb_stb);
initial br_pre_wb_ack = 1'b0;
initial br_wb_ack = 1'b0;
always @(posedge i_wb_clk)
begin
if ((bw_reset_request)
||((bw_cyc_stb)&&(i_wb_addr)&&(i_wb_we)))
raddr <= 0;
else if ((bw_cyc_stb)&&(i_wb_addr)&&(!i_wb_we)&&(bw_stopped))
raddr <= raddr + 1'b1; // Data read, when stopped
 
br_pre_wb_ack <= bw_cyc_stb;
br_wb_ack <= (br_pre_wb_ack)&&(i_wb_cyc);
end
 
 
 
reg [(LGMEM-1):0] this_addr;
always @(posedge i_wb_clk)
if ((bw_cyc_stb)&&(i_wb_addr)&&(!i_wb_we))
this_addr <= raddr + waddr + 1'b1;
else
this_addr <= raddr + waddr;
 
reg [31:0] nxt_mem;
always @(posedge i_wb_clk)
nxt_mem <= mem[this_addr];
 
wire [19:0] full_holdoff;
assign full_holdoff[(HOLDOFFBITS-1):0] = br_holdoff;
generate if (HOLDOFFBITS < 20)
assign full_holdoff[19:(HOLDOFFBITS)] = 0;
endgenerate
 
wire [4:0] bw_lgmem;
assign bw_lgmem = LGMEM;
always @(posedge i_wb_clk)
if (!i_wb_addr) // Control register read
o_wb_data <= { bw_reset_request,
bw_stopped,
bw_triggered,
bw_primed,
bw_manual_trigger,
bw_disable_trigger,
(raddr == {(LGMEM){1'b0}}),
bw_lgmem,
full_holdoff };
else if (!bw_stopped) // read, prior to stopping
o_wb_data <= {1'b0, w_data };// Violates clk tfr rules
else // if (i_wb_addr) // Read from FIFO memory
o_wb_data <= nxt_mem; // mem[raddr+waddr];
 
assign o_wb_stall = 1'b0;
assign o_wb_ack = (i_wb_cyc)&&(br_wb_ack);
 
reg br_level_interrupt;
initial br_level_interrupt = 1'b0;
assign o_interrupt = (bw_stopped)&&(!bw_disable_trigger)
&&(!br_level_interrupt);
always @(posedge i_wb_clk)
if ((bw_reset_complete)||(bw_reset_request))
br_level_interrupt<= 1'b0;
else
br_level_interrupt<= (bw_stopped)&&(!bw_disable_trigger);
 
// Make Verilator happy
// verilator lint_off UNUSED
wire [3+5+(20-HOLDOFFBITS)-1:0] unused;
assign unused = { i_wb_data[30:28], i_wb_data[25:HOLDOFFBITS] };
// verilator lint_on UNUSED
 
endmodule
/trunk/rtl/wbscope.v
27,7 → 27,7
// 5. The scope recording is then paused until the next reset.
// 6. While stopped, the CPU can read the data from the scope
// 7. -- oldest to most recent
// 8. -- one value per i_rd&i_clk
// 8. -- one value per i_rd&i_data_clk
// 9. Writes to the data register reset the address to the
// beginning of the buffer
//
83,7 → 83,9
////////////////////////////////////////////////////////////////////////////////
//
//
module wbscope(i_clk, i_ce, i_trigger, i_data,
`default_nettype none
//
module wbscope(i_data_clk, i_ce, i_trigger, i_data,
i_wb_clk, i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data,
o_wb_ack, o_wb_stall, o_wb_data,
o_interrupt);
93,19 → 95,45
parameter HOLDOFFBITS = 20;
parameter [(HOLDOFFBITS-1):0] DEFAULT_HOLDOFF = ((1<<(LGMEM-1))-4);
// The input signals that we wish to record
input i_clk, i_ce, i_trigger;
input [(BUSW-1):0] i_data;
input wire i_data_clk, i_ce, i_trigger;
input wire [(BUSW-1):0] i_data;
// The WISHBONE bus for reading and configuring this scope
input i_wb_clk, i_wb_cyc, i_wb_stb, i_wb_we;
input i_wb_addr; // One address line only
input [(BUSW-1):0] i_wb_data;
input wire i_wb_clk, i_wb_cyc, i_wb_stb, i_wb_we;
input wire i_wb_addr; // One address line only
input wire [(BUSW-1):0] i_wb_data;
output wire o_wb_ack, o_wb_stall;
output reg [(BUSW-1):0] o_wb_data;
output wire [(BUSW-1):0] o_wb_data;
// And, finally, for a final flair --- offer to interrupt the CPU after
// our trigger has gone off. This line is equivalent to the scope
// being stopped. It is not maskable here.
output wire o_interrupt;
 
wire bus_clock;
assign bus_clock = i_wb_clk;
 
///////////////////////////////////////////////////
//
// Decode and handle the WB bus signaling in a
// (somewhat) portable manner
//
///////////////////////////////////////////////////
//
//
assign o_wb_stall = 1'b0;
 
wire read_from_data;
assign read_from_data = (i_wb_stb)&&(!i_wb_we)&&(i_wb_addr);
 
wire write_stb;
assign write_stb = (i_wb_stb)&&(i_wb_we);
 
wire write_to_control;
assign write_to_control = (write_stb)&&(!i_wb_addr);
 
reg read_address;
always @(posedge bus_clock)
read_address <= i_wb_addr;
 
reg [(LGMEM-1):0] raddr;
reg [(BUSW-1):0] mem[0:((1<<LGMEM)-1)];
 
116,16 → 144,13
reg [(HOLDOFFBITS-1):0] br_holdoff;
initial br_config = 3'b0;
initial br_holdoff = DEFAULT_HOLDOFF;
always @(posedge i_wb_clk)
if ((i_wb_stb)&&(!i_wb_addr))
always @(posedge bus_clock)
if (write_to_control)
begin
if (i_wb_we)
begin
br_config <= { i_wb_data[31],
i_wb_data[27],
i_wb_data[26] };
br_holdoff = i_wb_data[(HOLDOFFBITS-1):0];
end
br_config <= { i_wb_data[31],
i_wb_data[27],
i_wb_data[26] };
br_holdoff <= i_wb_data[(HOLDOFFBITS-1):0];
end else if (bw_reset_complete)
br_config[2] <= 1'b1;
assign bw_reset_request = (!br_config[2]);
149,7 → 174,7
// so do a clock transfer here
initial q_iflags = 3'b000;
initial r_reset_complete = 1'b0;
always @(posedge i_clk)
always @(posedge i_data_clk)
begin
q_iflags <= { bw_reset_request, bw_manual_trigger, bw_disable_trigger };
r_iflags <= q_iflags;
166,7 → 191,7
// clock that the reset has been accomplished
initial q_reset_complete = 1'b0;
initial qq_reset_complete = 1'b0;
always @(posedge i_wb_clk)
always @(posedge bus_clock)
begin
q_reset_complete <= r_reset_complete;
qq_reset_complete <= q_reset_complete;
180,7 → 205,7
//
//
// Write with the i-clk, or input clock. All outputs read with the
// WISHBONE-clk, or i_wb_clk clock.
// bus clock, or bus_clock as we've called it here.
reg dr_triggered, dr_primed;
wire dw_trigger;
assign dw_trigger = (dr_primed)&&(
187,7 → 212,7
((i_trigger)&&(!dw_disable_trigger))
||(dw_manual_trigger));
initial dr_triggered = 1'b0;
always @(posedge i_clk)
always @(posedge i_data_clk)
if (dw_reset)
dr_triggered <= 1'b0;
else if ((i_ce)&&(dw_trigger))
203,20 → 228,20
reg dr_stopped;
initial dr_stopped = 1'b0;
initial counter = 0;
always @(posedge i_clk)
always @(posedge i_data_clk)
if (dw_reset)
counter <= 0;
else if ((i_ce)&&(dr_triggered)&&(!dr_stopped))
begin // MUST BE a < and not <=, so that we can keep this w/in
// 20 bits. Else we'd need to add a bit to comparison
// here.
begin
counter <= counter + 1'b1;
end
always @(posedge i_clk)
always @(posedge i_data_clk)
if ((!dr_triggered)||(dw_reset))
dr_stopped <= 1'b0;
else
else if (HOLDOFFBITS > 1) // if (i_ce)
dr_stopped <= (counter >= br_holdoff);
else if (HOLDOFFBITS <= 1)
dr_stopped <= ((i_ce)&&(dw_trigger));
 
//
// Actually do our writes to memory. Record, via 'primed' when
231,7 → 256,7
reg [(LGMEM-1):0] waddr;
initial waddr = {(LGMEM){1'b0}};
initial dr_primed = 1'b0;
always @(posedge i_clk)
always @(posedge i_data_clk)
if (dw_reset) // For simulation purposes, supply a valid value
begin
waddr <= 0; // upon reset.
240,11 → 265,48
begin
// mem[waddr] <= i_data;
waddr <= waddr + {{(LGMEM-1){1'b0}},1'b1};
dr_primed <= (dr_primed)||(&waddr);
if (!dr_primed)
begin
//if (br_holdoff[(HOLDOFFBITS-1):LGMEM]==0)
// dr_primed <= (waddr >= br_holdoff[(LGMEM-1):0]);
// else
dr_primed <= (&waddr);
end
end
always @(posedge i_clk)
 
// Delay the incoming data so that we can get our trigger
// logic to line up with the data. The goal is to have a
// hold off of zero place the trigger in the last memory
// address.
localparam STOPDELAY = 1;
wire [(BUSW-1):0] wr_piped_data;
generate
if (STOPDELAY == 0)
// No delay ... just assign the wires to our input lines
assign wr_piped_data = i_data;
else if (STOPDELAY == 1)
begin
//
// Delay by one means just register this once
reg [(BUSW-1):0] data_pipe;
always @(posedge i_data_clk)
if (i_ce)
data_pipe <= i_data;
assign wr_piped_data = data_pipe;
end else begin
// Arbitrary delay ... use a longer pipe
reg [(STOPDELAY*BUSW-1):0] data_pipe;
 
always @(posedge i_data_clk)
if (i_ce)
data_pipe <= { data_pipe[((STOPDELAY-1)*BUSW-1):0], i_data };
assign wr_piped_data = { data_pipe[(STOPDELAY*BUSW-1):((STOPDELAY-1)*BUSW)] };
end endgenerate
 
always @(posedge i_data_clk)
if ((i_ce)&&(!dr_stopped))
mem[waddr] <= i_data;
mem[waddr] <= wr_piped_data;
 
//
// Clock transfer of the status signals
266,7 → 328,7
reg [2:0] r_oflags;
initial q_oflags = 3'h0;
initial r_oflags = 3'h0;
always @(posedge i_wb_clk)
always @(posedge bus_clock)
if (bw_reset_request)
begin
q_oflags <= 3'h0;
282,33 → 344,34
end endgenerate
 
// Reads use the bus clock
reg br_wb_ack;
reg br_wb_ack, br_pre_wb_ack;
initial br_wb_ack = 1'b0;
wire bw_cyc_stb;
assign bw_cyc_stb = (i_wb_stb);
always @(posedge i_wb_clk)
initial br_pre_wb_ack = 1'b0;
initial br_wb_ack = 1'b0;
always @(posedge bus_clock)
begin
if ((bw_reset_request)
||((bw_cyc_stb)&&(i_wb_addr)&&(i_wb_we)))
if ((bw_reset_request)||(write_to_control))
raddr <= 0;
else if ((bw_cyc_stb)&&(i_wb_addr)&&(!i_wb_we)&&(bw_stopped))
else if ((read_from_data)&&(bw_stopped))
raddr <= raddr + 1'b1; // Data read, when stopped
 
if ((bw_cyc_stb)&&(!i_wb_we))
begin // Read from the bus
br_wb_ack <= 1'b1;
end else if ((bw_cyc_stb)&&(i_wb_we))
// We did this write above
br_wb_ack <= 1'b1;
else // Do nothing if either i_wb_cyc or i_wb_stb are low
br_wb_ack <= 1'b0;
br_pre_wb_ack <= bw_cyc_stb;
br_wb_ack <= (br_pre_wb_ack)&&(i_wb_cyc);
end
assign o_wb_ack = (i_wb_cyc)&&(br_wb_ack);
 
reg [(LGMEM-1):0] this_addr;
always @(posedge bus_clock)
if (read_from_data)
this_addr <= raddr + waddr + 1'b1;
else
this_addr <= raddr + waddr;
 
reg [31:0] nxt_mem;
always @(posedge i_wb_clk)
nxt_mem <= mem[raddr+waddr+
(((bw_cyc_stb)&&(i_wb_addr)&&(!i_wb_we)) ?
{{(LGMEM-1){1'b0}},1'b1} : { (LGMEM){1'b0}} )];
always @(posedge bus_clock)
nxt_mem <= mem[this_addr];
 
wire [19:0] full_holdoff;
assign full_holdoff[(HOLDOFFBITS-1):0] = br_holdoff;
316,11 → 379,12
assign full_holdoff[19:(HOLDOFFBITS)] = 0;
endgenerate
 
reg [31:0] o_bus_data;
wire [4:0] bw_lgmem;
assign bw_lgmem = LGMEM;
always @(posedge i_wb_clk)
if (!i_wb_addr) // Control register read
o_wb_data <= { bw_reset_request,
always @(posedge bus_clock)
if (!read_address) // Control register read
o_bus_data <= { bw_reset_request,
bw_stopped,
bw_triggered,
bw_primed,
330,21 → 394,25
bw_lgmem,
full_holdoff };
else if (!bw_stopped) // read, prior to stopping
o_wb_data <= i_data;
o_bus_data <= i_data;
else // if (i_wb_addr) // Read from FIFO memory
o_wb_data <= nxt_mem; // mem[raddr+waddr];
o_bus_data <= nxt_mem; // mem[raddr+waddr];
 
assign o_wb_stall = 1'b0;
assign o_wb_ack = (i_wb_cyc)&&(br_wb_ack);
assign o_wb_data = o_bus_data;
 
reg br_level_interrupt;
initial br_level_interrupt = 1'b0;
assign o_interrupt = (bw_stopped)&&(!bw_disable_trigger)
&&(!br_level_interrupt);
always @(posedge i_wb_clk)
always @(posedge bus_clock)
if ((bw_reset_complete)||(bw_reset_request))
br_level_interrupt<= 1'b0;
else
br_level_interrupt<= (bw_stopped)&&(!bw_disable_trigger);
 
// verilator lint_off UNUSED
// Make verilator happy
wire [28:0] unused;
assign unused = { i_wb_data[30:28], i_wb_data[25:0] };
// verilator lint_on UNUSED
endmodule
/trunk/sw/scopecls.cpp
56,6 → 56,7
v = m_fpga->readio(m_addr);
if (m_scoplen == 0) {
m_scoplen = (1<<((v>>20)&0x01f));
m_holdoff = (v & ((1<<20)-1));
} v = (v>>28)&6;
return (v==6);
}
64,6 → 65,7
unsigned v;
 
v = m_fpga->readio(m_addr);
printf("\tCNTRL-REG:\t0x%08x\n", v);
printf("\t31. RESET:\t%s\n", (v&0x80000000)?"Ongoing":"Complete");
printf("\t30. STOPPED:\t%s\n", (v&0x40000000)?"Yes":"No");
printf("\t29. TRIGGERED:\t%s\n", (v&0x20000000)?"Yes":"No");
84,6 → 86,7
// looked up the length by reading from the scope.
if (m_scoplen == 0) {
v = m_fpga->readio(m_addr);
m_holdoff = (v & ((1<<20)-1));
 
// Since the length of the scope memory is a configuration
// parameter internal to the scope, we read it here to find
98,7 → 101,7
// that to the actual length of the scope.
m_scoplen = (1<<lgln);
}
// else we already know the length of the scope, and don't need to
// else we already know the length of the scope, and don't need to
// slow down to read that length from the device a second time.
} return m_scoplen;
}
141,21 → 144,32
}
 
void SCOPE::print(void) {
DEVBUS::BUSW addrv = 0;
unsigned long addrv = 0, alen;
int offset;
 
rawread();
 
// Count how many values are in our (possibly compressed) buffer.
// If it weren't for the compression, this'd be m_scoplen
alen = getaddresslen();
 
// If the holdoff is zero, the triggered item is the very
// last one.
offset = alen - m_holdoff -1;
 
if(m_compressed) {
for(int i=0; i<(int)m_scoplen; i++) {
if ((m_data[i]>>31)&1) {
addrv += (m_data[i]&0x7fffffff);
addrv += (m_data[i]&0x7fffffff) + 1;
printf(" ** (+0x%08x = %8d)\n",
(m_data[i]&0x07fffffff),
(m_data[i]&0x07fffffff));
continue;
}
printf("%10d %08x: ", addrv++, m_data[i]);
printf("%10ld %08x: ", addrv++, m_data[i]);
decode(m_data[i]);
if ((int)addrv == offset)
printf(" <--- TRIGGER");
printf("\n");
}
} else {
166,6 → 180,9
continue;
} printf("%9d %08x: ", i, m_data[i]);
decode(m_data[i]);
 
if (i == offset)
printf(" <--- TRIGGER");
printf("\n");
}
}
172,11 → 189,20
}
 
void SCOPE::write_trace_timescale(FILE *fp) {
fprintf(fp, "$timescle 1ns $end\n\n");
fprintf(fp, "$timescale 1ns $end\n\n");
}
 
void SCOPE::write_trace_timezero(FILE *fp, int offset) {
double dwhen;
long when_ns;
 
dwhen = 1.0/((double)m_clkfreq_hz) * (offset);
when_ns = (unsigned long)(dwhen * 1e9);
fprintf(fp, "$timezero %ld $end\n\n", -when_ns);
}
 
// $dumpoff and $dumpon
void SCOPE::write_trace_header(FILE *fp) {
void SCOPE::write_trace_header(FILE *fp, int offset) {
time_t now;
 
time(&now);
183,14 → 209,25
fprintf(fp, "$version Generated by WBScope $end\n");
fprintf(fp, "$date %s\n $end\n", ctime(&now));
write_trace_timescale(fp);
if (offset != 0)
write_trace_timezero(fp, offset);
 
fprintf(fp, " $scope module WBSCOPE $end\n");
// Print out all of the various values
fprintf(fp, " $var wire %2d \'C clk $end\n", 1);
fprintf(fp, " $var wire %2d \'R _raw_data [%d:0] $end\n",
(m_compressed)?31:32,
(m_compressed)?30:31);
if (m_compressed) {
fprintf(fp, " $var wire %2d \'R _raw_data [%d:0] $end\n", 31,
30);
} else {
fprintf(fp, " $var wire %2d \'C clk $end\n", 1);
fprintf(fp, " $var wire %2d \'R _raw_data [%d:0] $end\n", 32,
31);
}
 
// Add in a fake _trigger variable to the VCD file we are producing,
// so we can see when our trigger took place (assuming the holdoff is
// such that it is within the collect)
fprintf(fp, " $var wire %2d \'T _trigger $end\n", 1);
 
for(unsigned i=0; i<m_traces.size(); i++) {
TRACEINFO *info = m_traces[i];
fprintf(fp, " $var wire %2d %s %s",
246,8 → 283,185
m_traces.push_back(info);
}
 
/*
* getaddresslen(void)
*
* Returns the number of items in the scope's buffer. For the uncompressed
* scope, this is just the size of hte scope. For the compressed scope ... this
* is a touch longer.
*/
unsigned SCOPE::getaddresslen(void) {
// Find the offset to the trigger
if (m_compressed) {
// First, find the overall length
//
// If we are compressed, then *every* item increments
// the address length
unsigned alen = m_scoplen;
//
// Some items increment it more.
for(int i=0; i<(int)m_scoplen; i++) {
if ((m_data[i]&0x80000000)&&(i!=0))
alen += m_data[i] & 0x7fffffff;
}
 
return alen;
} return m_scoplen;
}
 
/*
* define_traces
*
* This is a user stub. User programs should define this function.
*/
void SCOPE::define_traces(void) {}
 
void SCOPE::writevcd(FILE *fp) {
unsigned alen;
int offset = 0;
 
if (!m_data)
rawread();
 
// If the traces haven't yet been defined, then define them now.
if (m_traces.size()==0)
define_traces();
 
// Count how many values are in our (possibly compressed) buffer.
// If it weren't for the compression, this'd be m_scoplen
alen = getaddresslen();
 
// If the holdoff is zero, the triggered item is the very
// last one.
offset = alen - m_holdoff -1;
 
// Write the file header.
write_trace_header(fp, offset);
 
// And split into two paths--one for compressed scopes (wbscopc), and
// the other for the more normal scopes (wbscope).
if(m_compressed) {
// With compressed scopes, you need to track the address
// relative to the beginning.
unsigned long addrv = 0;
unsigned long now_ns;
double dnow;
bool last_trigger = true;
 
// Loop over each data word read from the scope
for(int i=0; i<(int)m_scoplen; i++) {
// If the high bit is set, the address jumps by more
// than an increment
if ((m_data[i]>>31)&1) {
if (i!=0) {
if (last_trigger) {
// If the trigger was valid
// on the last clock, then we
// need to include the change
// to drop it.
//
dnow = 1.0/((double)m_clkfreq_hz) * (addrv+1);
now_ns = (unsigned long)(dnow * 1e9);
fprintf(fp, "#%ld\n", now_ns);
fprintf(fp, "0\'T\n");
}
// But ... with nothing to write out.
addrv += (m_data[i]&0x7fffffff) + 1;
} continue;
}
 
// Produce a line identifying the time associated with
// this piece of data.
//
// dnow is the current time represented as a double
dnow = 1.0/((double)m_clkfreq_hz) * addrv;
// Convert to nanoseconds, and to integers.
now_ns = (unsigned long)(dnow * 1e9);
 
fprintf(fp, "#%ld\n", now_ns);
 
if ((int)(addrv-alen) == offset) {
fprintf(fp, "1\'T\n");
last_trigger = true;
} else if (last_trigger)
fprintf(fp, "0\'T\n");
 
// For compressed data, only the lower 31 bits are
// valid. Write those bits to the VCD file as a raw
// value.
write_binary_trace(fp, 31, m_data[i], "\'R\n");
 
// Finally, walk through all of the user defined traces,
// writing each to the VCD file.
for(unsigned k=0; k<m_traces.size(); k++) {
TRACEINFO *info = m_traces[k];
write_binary_trace(fp, info, m_data[i]);
}
 
addrv++;
}
} else {
//
// Uncompressed scope.
//
unsigned now_ns;
double dnow;
 
// We assume a clock signal, and set it to one and zero.
// We also assume everything changes on the positive edge of
// that clock within here.
 
// Loop over all data words
for(int i=0; i<(int)m_scoplen; i++) {
// Positive edge of the clock (everything is assumed to
// be on the positive edge)
 
 
//
// Clock goes high
//
 
// Write the current (relative) time of this data word
dnow = 1.0/((double)m_clkfreq_hz) * i;
now_ns = (unsigned)(dnow * 1e9 + 0.5);
fprintf(fp, "#%d\n", now_ns);
 
fprintf(fp, "1\'C\n");
write_binary_trace(fp, (m_compressed)?31:32,
m_data[i], "\'R\n");
 
if (i == offset)
fprintf(fp, "1\'T\n");
else // if (addrv == offset+1)
fprintf(fp, "0\'T\n");
 
for(unsigned k=0; k<m_traces.size(); k++) {
TRACEINFO *info = m_traces[k];
write_binary_trace(fp, info, m_data[i]);
}
 
//
// Clock goes to zero
//
 
// Add half a clock period to our time
dnow += 1.0/((double)m_clkfreq_hz)/2.;
now_ns = (unsigned)(dnow * 1e9 + 0.5);
fprintf(fp, "#%d\n", now_ns);
 
// Now finally write the clock as zero.
fprintf(fp, "0\'C\n");
}
}
}
 
/*
* writevcd
*
* Main user entry point for VCD file creation. This just opens a file of the
* given name, and writes the VCD info to it. If the file cannot be opened,
* an error is written to the standard error stream, and the routine returns.
*/
void SCOPE::writevcd(const char *trace_file_name) {
FILE *fp = fopen(trace_file_name, "w");
 
257,27 → 471,8
return;
}
 
if (!m_data)
rawread();
writevcd(fp);
 
write_trace_header(fp);
 
for(int i=0; i<(int)m_scoplen; i++) {
// Positive edge of the clock (everything is assumed to
// be on the positive edge)
fprintf(fp, "#%d\n", m_scoplen * 10);
fprintf(fp, "1\'C\n");
write_binary_trace(fp, (m_compressed)?31:32,
m_data[i], "\'R\n");
 
for(unsigned k=0; k<m_traces.size(); k++) {
TRACEINFO *info = m_traces[k];
write_binary_trace(fp, info, m_data[i]);
}
 
// Clock goes to zero
fprintf(fp, "#%d\n", m_scoplen * 10 + 5);
fprintf(fp, "0\'C\n");
}
fclose(fp);
}
 
/trunk/sw/scopecls.h
7,8 → 7,11
// Purpose: After rebuilding the same code over and over again for every
// "scope" I tried to interact with, I thought it would be simpler
// to try to make a more generic interface, that other things could plug
// into. This is that more generic interface.
// into. This file defines and describes that more generic interface.
//
// More recent updates have added to this interface those things necessary
// to create a .VCD file for viewing in GTKWave.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
44,6 → 47,20
#include <vector>
#include "devbus.h"
 
 
/*
* TRACEINFO
*
* The TRACEINFO class describes a wire (or set of wires) internal to the
* scope data word. These wires are assumed to be contiguous, and given by:
* ((data_word>>m_nshift)&((1<<m_nbits)-1). That is, there are m_nbits bits
* to this value, and a shift of m_nshift is required to bring them down to
* zero.
*
* Other key pieces include the human readable name given to the signal, m_name,
* as well as the VCD name, m_key.
*
*/
class TRACEINFO {
public:
const char *m_name;
51,12 → 68,30
unsigned m_nbits, m_nshift;
};
 
/*
* SCOPE
*
* This class is designed to be a generic SCOPE class, one which has all of the
* logic other scopes will require. Hence, if more than one scope needs this
* logic, I stuff it in here for all scopes to use.
*/
class SCOPE {
DEVBUS *m_fpga;
DEVBUS::BUSW m_addr;
bool m_compressed, m_vector_read;
unsigned m_scoplen;
unsigned *m_data;
DEVBUS *m_fpga; // Access to the FPGA
DEVBUS::BUSW m_addr; // The address of the scope control reg
// Set m_compressed to be true if the scope is a
// compressed scope, that is if it uses the wbscopc.v
// core.
bool m_compressed,
// Set m_vector_read if you trust the bus enough to
// issue vector reads (multiple words at once)
m_vector_read;
unsigned m_scoplen, // Number of words in the scopes memory
m_holdoff; // The bias, or samples since trigger
unsigned *m_data; // Data read from the scope
unsigned m_clkfreq_hz;
 
// The m_traces variable holds a list of all of the various wire
// definitions within the scope data word.
std::vector<TRACEINFO *> m_traces;
 
public:
64,25 → 99,131
bool compressed=false, bool vecread=true)
: m_fpga(fpga), m_addr(addr),
m_compressed(compressed), m_vector_read(vecread),
m_scoplen(0), m_data(NULL) {}
~SCOPE(void) { if (m_data) delete[] m_data; }
m_scoplen(0), m_data(NULL) {
//
// First thing we want to do upon allocating a scope, is to
// define the traces for that scope. Sad thing is ... we can't
// call it here, since the class inheriting from us isn't
// defined yet.
// define_traces();
 
 
// Default clock frequency: 100MHz.
m_clkfreq_hz = 100000000;
}
 
// Free up any of our allocated memory.
~SCOPE(void) {
for(unsigned i=0; i<m_traces.size(); i++)
delete m_traces[i];
if (m_data) delete[] m_data;
}
 
// Query the scope: Is it ready? Has it primed, triggered, and stopped?
// If so, this routine returns true, false otherwise.
bool ready();
 
// Read the control word from the scope, and send to the standard output
// a description of that.
void decode_control(void);
 
// Read the scope's control word, decode the memory size of the scope,
// and return that to our caller.
int scoplen(void);
 
// Set the clock speed that we are referencing
void set_clkfreq_hz(unsigned clkfreq_hz) {
m_clkfreq_hz = clkfreq_hz;
}
 
// Read any previously set clock speed.
unsigned get_clkfreq_hz(void) { return m_clkfreq_hz; }
 
// Read the data from the scope and place it into our m_data array.
// Nothing more is done with it beyond that.
virtual void rawread(void);
 
// Walk through the data, and print out to the standard output, what is
// in it. If multiple lines have the same data, print() will avoid
// printing those lines for the purpose of keeping the output from
// getting cluttered, but it will print a **** spacer, so you know
// lines were skipped.
void print(void);
 
// decode() works together with print() above. The print() routine
// calls decode() for every memory word within the scope's buffer.
// More than that, the print() routine starts each line with the
// clock number of the item, followed by the 32-bit data word in hex and
// a colon. Then it calls decode() to fill in the line with whatever
// useful information was in the scope's data word. Then it prints
// a "\n" and continues. Hence ... the purpose of the decode()
// function--and why it needs to be scope specific.
virtual void decode(DEVBUS::BUSW v) const = 0;
 
//
//
// The following routines are provided to enable the creation and
// writing of VCD files.
//
//
 
// Write the timescale line to a VCD file.
virtual void write_trace_timescale(FILE *fp);
virtual void write_trace_header(FILE *fp);
 
// Write the offset from the time within the file, to the time of the
// trigger, into the file.
virtual void write_trace_timezero(FILE *fp, int offset);
 
// Write the VCD file's header
virtual void write_trace_header(FILE *fp, int offset = 0);
 
// Given a value, and the number of bits required to define that value,
// write a single line to our VCD file.
//
// This is an internal call that you are not likely to need to modify.
void write_binary_trace(FILE *fp, const int nbits,
unsigned val, const char *str);
 
// Same as write_binary_trace above, but this time we are given the
// trace definition and the un-decomposed word to decompose first before
// writing to the file.
//
// This is also an internal call that you are not likely to need to
// modify.
void write_binary_trace(FILE *fp, TRACEINFO *info,
unsigned value);
 
// This is the user entry point. When you know the scope is ready,
// you may call writevcd to start the VCD generation process.
void writevcd(const char *trace_file_name);
// This is an alternate entry point, useful if you already have a
// FILE *. This will write the data to the file, but not close the
// file.
void writevcd(FILE *fp);
 
// Calculate the number of points the scope covers. Nominally, this
// will be m_scopelen, the length of the scope. However, if the
// scope is compressed, this could be greater.
//
unsigned getaddresslen(void);
 
// Your program needs to define a define_traces() function, which will
// then be called before trying to write the VCD file. This function
// must call register_trace for each of the traces within your data
// word.
virtual void define_traces(void);
 
// Register_trace() defines the elements of a TRACEINFO structure
// above. These are then inserted into the list of TRACEINFO
// structures, for reference when writing the VCD file.
void register_trace(const char *varname,
unsigned nbits, unsigned shift);
virtual void decode(DEVBUS::BUSW v) const = 0;
virtual void define_traces(void);
 
unsigned operator[](unsigned addr) {
if ((m_data)&&(m_scoplen > 0))
return m_data[(addr)&(m_scoplen-1)];
return 0;
}
};
 
#endif // SCOPECLS_H

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.