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 11 to Rev 12
- ↔ Reverse comparison
Rev 11 → Rev 12
/trunk/README.md
0,0 → 1,32
# A Wishbone Controlled Scope for FPGA's |
|
This is a generic/library routine for providing a bus accessed 'scope' or |
(perhaps more appropriately) a bus accessed logic analyzer for use internal to |
an FPGA. The general operation is such that this 'scope' can record and report |
on any 32 bit value transiting through the FPGA. Once started and 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 from the buffer, oldest to most |
recent. After reading, the scope may then be reset for another run. |
|
In general, therefore, operation happens in this fashion: |
|
1. A reset is issued. |
2. Recording starts, in a circular buffer, and continues until |
3. The trigger line is asserted. |
The scope registers the asserted trigger by setting the ``o_triggered`` output flag. |
4. A counter then ticks until the last value is written. |
The scope registers that it has stopped recording by setting the ``o_stopped`` output flag. |
5. The scope recording is then paused until the next reset. |
6. While stopped, the CPU can read the data from the scope |
|
- oldest to most recent |
- one value per bus clock |
|
7. Writes to the data register reset the address to the beginning of the buffer |
|
# Commercial Applications |
|
Should you find the GPLv3 license insufficient for your needs, other licenses |
can be purchased from Gisselquist Technology, LLC. |
/trunk/bench/cpp/Makefile
0,0 → 1,49
################################################################################ |
## |
## Filename: |
## |
## Project: WBScope, a wishbone hosted scope |
## |
## Purpose: |
## |
## 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: wbscope_tb |
CXX := g++ |
RTLD := ../rtl |
ROBJD:= $(RTLD)/obj_dir |
VROOT:= /usr/share/verilator |
VINCS:= -I$(VROOT) |
INCS := -I$(VROOT)/include -I$(ROBJD) |
VSRCS:= $(VROOT)/include/verilated.cpp $(VROOT)/include/verilated_vcd_c.cpp |
TBOBJ:= $(ROBJD)/Vwbscope_tb__ALL.a |
|
wbscope_tb: wbscope_tb.cpp $(ROBJD)/Vwbscope_tb__ALL.a $(ROBJD)/Vwbscope_tb.h |
$(CXX) $(INCS) wbscope_tb.cpp $(VSRCS) $(TBOBJ) -o $@ |
/trunk/bench/cpp/testb.h
0,0 → 1,104
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: testb.h |
// |
// Project: Zip CPU -- a small, lightweight, RISC CPU core |
// |
// Purpose: A wrapper for a common interface to a clocked FPGA core |
// begin exercised in Verilator. |
// |
// 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 TESTB_H |
#define TESTB_H |
|
#include <stdio.h> |
#include <stdint.h> |
#include <verilated_vcd_c.h> |
|
template <class VA> class TESTB { |
public: |
VA *m_core; |
VerilatedVcdC* m_trace; |
unsigned long m_tickcount; |
|
TESTB(void) : m_trace(NULL), m_tickcount(0l) { |
m_core = new VA; |
Verilated::traceEverOn(true); |
} |
virtual ~TESTB(void) { |
if (m_trace) m_trace->close(); |
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); |
} |
|
virtual void closetrace(void) { |
if (m_trace) { |
m_trace->close(); |
m_trace = NULL; |
} |
} |
|
virtual void eval(void) { |
m_core->eval(); |
} |
|
virtual void tick(void) { |
m_tickcount++; |
|
//if((m_trace)&&(m_tickcount)) m_trace->dump(10*m_tickcount-4); |
eval(); |
if ((m_trace)&&(m_tickcount)) 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); |
|
} |
|
virtual void reset(void) { |
m_core->i_rst = 1; |
tick(); |
m_core->i_rst = 0; |
// printf("RESET\n"); |
} |
|
unsigned long tickcount(void) { |
return m_tickcount; |
} |
}; |
|
#endif |
/trunk/bench/cpp/wbscope_tb.cpp
0,0 → 1,282
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: wbscope_tb.cpp |
// |
// Project: WBScope, a wishbone hosted scope |
// |
// Purpose: A quick test bench to determine if the wbscope 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 "testb.h" |
#include "Vwbscope_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 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; |
public: |
|
WBSCOPE_TB(void) { |
m_debug = true; |
m_last_tlb_index = 0; |
} |
|
void tick(void) { |
|
TESTB<Vwbscope_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; |
} |
|
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(); |
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); |
WBSCOPE_TB *tb = new WBSCOPE_TB; |
unsigned v; |
unsigned *buf; |
int trigpt; |
|
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(); |
|
#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->wb_read(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)]; |
|
for(int i=0; i<(1<<ln); i++) |
tb->wb_tick(); |
|
v = tb->wb_read(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->wb_read(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->wb_read(WBSCOPE_STATUS); |
printf("SCOPE has stopped, reading data\n"); |
|
tb->wb_read(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)) |
goto test_failure; |
} |
|
trigpt = (1<<ln)-v&(0x0fffff); |
if ((trigpt >= 0)&&(trigpt < (1<<ln))) { |
printf("Trigger value = %08x\n", buf[trigpt]); |
if (((0x80000000 & buf[trigpt])==0)&&(trigpt>0)) { |
printf("Pre-Trigger value = %08x\n", buf[trigpt-1]); |
if ((buf[trigpt-1]&0x80000000)==0) { |
printf("TRIGGER NOT FOUND\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
Property changes :
Added: svn:ignore
## -0,0 +1,2 ##
+*_tb
+*.vcd
Index: trunk/bench/rtl/Makefile
===================================================================
--- trunk/bench/rtl/Makefile (nonexistent)
+++ trunk/bench/rtl/Makefile (revision 12)
@@ -0,0 +1,64 @@
+################################################################################
+#
+# 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.
+##
+##
+## 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
+## for a copy.
+##
+## License: GPL, v3, as defined and found on www.gnu.org,
+## http://www.gnu.org/licenses/gpl.html
+##
+##
+################################################################################
+##
+##
+.PHONY: all
+all: wbscope_tb
+
+RTLD := ../../rtl
+VOBJ := obj_dir
+
+$(VOBJ)/Vwbscope_tb.cpp: $(RTLD)/wbscope.v wbscope_tb.v
+ verilator -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
+ make --no-print-directory --directory=$(VOBJ) -f Vwbscope_tb.mk
+
+.PHONY: wbscope_tb
+wbscope_tb: $(VOBJ)/Vwbscope_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
+
+.PHONY: clean
+clean:
+ rm -rf $(VOBJ)
Index: trunk/bench/rtl/wbscope_tb.v
===================================================================
--- trunk/bench/rtl/wbscope_tb.v (nonexistent)
+++ trunk/bench/rtl/wbscope_tb.v (revision 12)
@@ -0,0 +1,69 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// Filename: wbscope_tb.v
+//
+// Project: WBScope, a wishbone hosted scope
+//
+// Purpose:
+//
+// 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
+// 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, 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);
+ input i_clk, i_rst, i_trigger;
+ output wire [31:0] o_data;
+ //
+ 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 o_interrupt;
+
+ reg [30:0] counter;
+ initial counter = 0;
+ always @(posedge i_clk)
+ counter <= counter + 1'b1;
+
+ assign o_data = { i_trigger, counter };
+
+ wire wb_stall_ignored;
+
+ wbscope #(5'd6, 32, 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,
+ o_wb_ack, wb_stall_ignored, o_wb_data,
+ o_interrupt);
+
+endmodule
Index: trunk/bench/rtl
===================================================================
--- trunk/bench/rtl (nonexistent)
+++ trunk/bench/rtl (revision 12)
trunk/bench/rtl
Property changes :
Added: svn:ignore
## -0,0 +1 ##
+obj_dir
Index: trunk/doc/Makefile
===================================================================
--- trunk/doc/Makefile (revision 11)
+++ trunk/doc/Makefile (revision 12)
@@ -15,6 +15,6 @@
ps2pdf -dAutoRotatePages=/All spec.ps spec.pdf
rm $(DSRC)/spec.dvi $(DSRC)/spec.log
rm $(DSRC)/spec.aux $(DSRC)/spec.toc
- rm $(DSRC)/spec.lot # $(DSRC)/spec.lof
+ rm $(DSRC)/spec.lot $(DSRC)/spec.out
rm spec.ps
/trunk/doc/gpl-3.0.pdf
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/doc/spec.pdf
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/doc/src/gqtekspec.cls
29,6 → 29,8
\usepackage[dvips]{pstricks} |
\usepackage{hhline} |
\usepackage{colortbl} |
\definecolor{webgreen}{rgb}{0,0.5,0} |
\usepackage[dvips,colorlinks=true,linkcolor=webgreen]{hyperref} |
\newdateformat{headerdate}{\THEYEAR/\twodigit{\THEMONTH}/\twodigit{\THEDAY}} |
\setlength{\hoffset}{0.25in} |
\setlength{\voffset}{-0.5in} |
/trunk/doc/src/spec.tex
45,7 → 45,7
\project{Wishbone Scope} |
\title{Specification} |
\author{Dan Gisselquist, Ph.D.} |
\email{dgisselq (at) opencores.org} |
\email{dgisselq (at) ieee.org} |
\revision{Rev.~0.1} |
\begin{document} |
\pagestyle{gqtekspecplain} |
64,7 → 64,7
for more details. |
|
You should have received a copy of the GNU General Public License along |
with this program. If not, see \hbox{<http://www.gnu.org/licenses/>} for a |
with this program. If not, see \texttt{http://www.gnu.org/licenses/} for a |
copy. |
\end{license} |
\begin{revisionhistory} |
341,7 → 341,7
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. |
\chapter{IO Ports} |
\chapter{I/O Ports}\label{ch:ioports} |
|
The ports are listed in Table.~\ref{tbl:ioports}. |
\begin{table}[htbp] |
/trunk/rtl/axi4lscope.v
0,0 → 1,562
`timescale 1 ns / 1 ps |
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: axi4lscope.v |
// |
// Project: FPGA Library of Routines |
// |
// Purpose: This is a generic/library routine for providing a bus accessed |
// 'scope' or (perhaps more appropriately) a bus accessed logic analyzer. |
// The general operation is such that this 'scope' can record and report |
// on any 32 bit value transiting through the FPGA. Once started and |
// 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 |
// from the buffer, oldest to most recent. After reading, the scope may |
// then be reset for another run. |
// |
// In general, therefore, operation happens in this fashion: |
// 1. A reset is issued. |
// 2. Recording starts, in a circular buffer, and continues until |
// 3. The trigger line is asserted. |
// The scope registers the asserted trigger by setting |
// the 'o_triggered' output flag. |
// 4. A counter then ticks until the last value is written |
// The scope registers that it has stopped recording by |
// setting the 'o_stopped' output flag. |
// 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 |
// 9. Writes to the data register reset the address to the |
// beginning of the buffer |
// |
// Although the data width DW is parameterized, it is not very changable, |
// since the width is tied to the width of the data bus, as is the |
// control word. Therefore changing the data width would require changing |
// the interface. It's doable, but it would be a change to the interface. |
// |
// The SYNCHRONOUS parameter turns on and off meta-stability |
// synchronization. Ideally a wishbone scope able to handle one or two |
// clocks would have a changing number of ports as this SYNCHRONOUS |
// parameter changed. Other than running another script to modify |
// this, I don't know how to do that so ... we'll just leave it running |
// off of two clocks or not. |
// |
// |
// Internal to this routine, registers and wires are named with one of the |
// following prefixes: |
// |
// i_ An input port to the routine |
// o_ An output port of the routine |
// br_ A register, controlled by the bus clock |
// dr_ A register, controlled by the data clock |
// bw_ A wire/net, controlled by the bus clock |
// dw_ A wire/net, controlled by the data clock |
// |
// And, of course, since AXI wants to be particular about their port |
// naming conventions, anything beginning with |
// |
// S_AXI_ |
// |
// is a signal associated with this function as an AXI slave. |
// |
// |
// 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 |
// |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
module axi4lscope |
#( |
// Users to add parameters here |
parameter LGMEM = 5'd10, |
parameter BUSW = 32, |
parameter SYNCHRONOUS=1, |
parameter DEFAULT_HOLDOFF = ((1<<(LGMEM-1))-4), |
// User parameters ends |
// DO NOT EDIT BELOW THIS LINE --------------------- |
// Do not modify the parameters beyond this line |
// Width of S_AXI data bus |
parameter integer C_S_AXI_DATA_WIDTH = 32, |
// Width of S_AXI address bus |
parameter integer C_S_AXI_ADDR_WIDTH = 4 |
) |
( |
// Users to add ports here |
input wire i_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, |
output wire o_interrupt, // ='1' when scope has stopped |
// User ports ends |
// DO NOT EDIT BELOW THIS LINE --------------------- |
// Do not modify the ports beyond this line |
// Global Clock Signal |
input wire S_AXI_ACLK, |
// Global Reset Signal. This Signal is Active LOW |
input wire S_AXI_ARESETN, |
// Write address (issued by master, acceped by Slave) |
input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR, |
// Write channel Protection type. This signal indicates the |
// privilege and security level of the transaction, and whether |
// the transaction is a data access or an instruction access. |
input wire [2 : 0] S_AXI_AWPROT, |
// Write address valid. This signal indicates that the master |
// signaling valid write address and control information. |
input wire S_AXI_AWVALID, |
// Write address ready. This signal indicates that the slave |
// is ready to accept an address and associated control signals. |
output wire S_AXI_AWREADY, |
// Write data (issued by master, acceped by Slave) |
input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA, |
// Write strobes. This signal indicates which byte lanes hold |
// valid data. There is one write strobe bit for each eight |
// bits of the write data bus. |
input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB, |
// Write valid. This signal indicates that valid write |
// data and strobes are available. |
input wire S_AXI_WVALID, |
// Write ready. This signal indicates that the slave |
// can accept the write data. |
output wire S_AXI_WREADY, |
// Write response. This signal indicates the status |
// of the write transaction. |
output wire [1 : 0] S_AXI_BRESP, |
// Write response valid. This signal indicates that the channel |
// is signaling a valid write response. |
output wire S_AXI_BVALID, |
// Response ready. This signal indicates that the master |
// can accept a write response. |
input wire S_AXI_BREADY, |
// Read address (issued by master, acceped by Slave) |
input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR, |
// Protection type. This signal indicates the privilege |
// and security level of the transaction, and whether the |
// transaction is a data access or an instruction access. |
input wire [2 : 0] S_AXI_ARPROT, |
// Read address valid. This signal indicates that the channel |
// is signaling valid read address and control information. |
input wire S_AXI_ARVALID, |
// Read address ready. This signal indicates that the slave is |
// ready to accept an address and associated control signals. |
output wire S_AXI_ARREADY, |
// Read data (issued by slave) |
output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA, |
// Read response. This signal indicates the status of the |
// read transfer. |
output wire [1 : 0] S_AXI_RRESP, |
// Read valid. This signal indicates that the channel is |
// signaling the required read data. |
output wire S_AXI_RVALID, |
// Read ready. This signal indicates that the master can |
// accept the read data and response information. |
input wire S_AXI_RREADY |
// DO NOT EDIT ABOVE THIS LINE --------------------- |
); |
|
// AXI4LITE signals |
reg [C_S_AXI_ADDR_WIDTH-1 : 0] axi_awaddr; |
reg axi_awready; |
reg axi_wready; |
// reg [1 : 0] axi_bresp; |
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; |
|
|
/////////////////////////////////////////////////// |
// |
// Decode and handle the AXI/Bus signaling |
// |
/////////////////////////////////////////////////// |
// |
// Sadly, the AXI bus is *way* more complicated to |
// deal with than it needs to be. Still, we offer |
// the following as a simple means of dealing with |
// it. The majority of the code in this section |
// comes directly from a Xilinx/Vivado generated |
// file. |
// |
// Gisselquist Technology, LLC, claims no copyright |
// or ownership of this section of the code. |
// |
wire i_reset; |
assign i_reset = !S_AXI_ARESETN; |
|
always @(posedge S_AXI_ACLK) |
if (i_reset) |
axi_awready <= 1'b0; |
else if ((!axi_awready)&&(S_AXI_AWVALID)&&(S_AXI_WVALID)) |
axi_awready <= 1'b1; |
else |
axi_awready <= 1'b0; |
assign S_AXI_AWREADY = axi_awready; |
|
always @(posedge S_AXI_ACLK) |
if ((!axi_awready)&&(S_AXI_AWVALID)&&(S_AXI_WVALID)) |
axi_awaddr <= S_AXI_AWADDR; |
|
always @(posedge S_AXI_ACLK) |
if (i_reset) |
axi_wready <= 1'b0; |
else if ((!axi_wready)&&(S_AXI_WVALID)&&(S_AXI_AWVALID)) |
axi_wready <= 1'b1; |
else |
axi_wready <= 1'b0; |
assign S_AXI_WREADY = axi_wready; |
|
wire write_stb; |
|
always @(posedge S_AXI_ACLK) |
if (i_reset) |
begin |
axi_bvalid <= 0; |
// axi_bresp <= 2'b00; |
end else if ((~axi_bvalid)&&(write_stb)) |
begin |
axi_bvalid <= 1'b1; |
// axi_bresp <= 2'b00; // 'Okay' response |
end else if ((S_AXI_BREADY)&&(axi_bvalid)) |
axi_bvalid <= 1'b0; |
assign S_AXI_BRESP = 2'b00; // An 'OKAY' response |
assign S_AXI_BVALID= axi_bvalid; |
|
|
|
always @(posedge S_AXI_ACLK) |
if (i_reset) |
begin |
axi_arready <= 1'b0; |
axi_araddr <= 0; |
end else if ((!axi_arready)&&(S_AXI_ARVALID)) |
begin |
axi_arready <= 1'b1; |
axi_araddr <= S_AXI_ARADDR; |
end else |
axi_arready <= 1'b0; |
assign S_AXI_ARREADY = axi_arready; |
|
always @(posedge S_AXI_ACLK) |
if (i_reset) |
begin |
axi_rvalid <= 0; |
// axi_rresp <= 0; |
end else if ((axi_arready)&&(S_AXI_ARVALID)&&(!axi_rvalid)) |
begin |
axi_rvalid <= 1'b0; |
// axi_rresp <= 2'b00; |
end else if ((axi_rvalid)&&(S_AXI_RREADY)) |
axi_rvalid <= 1'b0; |
assign S_AXI_RVALID = axi_rvalid; |
assign S_AXI_RRESP = 2'b00; |
|
|
|
|
/////////////////////////////////////////////////// |
// |
// Final simplification of the AXI code |
// |
/////////////////////////////////////////////////// |
// |
// Now that we've provided all of the bus signaling |
// above, can we make any sense of it? |
// |
// The following wires are here to provide some |
// simplification of the complex bus protocol. In |
// particular, are we reading or writing during this |
// clock? The two *should* be mutually exclusive |
// (i.e., you *shouldn't* be able to both read and |
// write on the same clock) ... but Xilinx's default |
// implementation does nothing to ensure that this |
// would be the case. |
// |
// From here on down, Gisselquist Technology, LLC, |
// claims a copyright on the code. |
// |
wire read_from_data; |
assign read_from_data = (S_AXI_ARVALID)&&(S_AXI_ARREADY) |
&&(axi_araddr[0]); |
|
assign write_stb = ((axi_awready)&&(S_AXI_AWVALID) |
&&(axi_wready)&&(S_AXI_WVALID)); |
wire write_to_control; |
assign write_to_control = (write_stb)&&(!axi_awaddr[0]); |
|
|
wire [31:0] i_wb_data; |
assign i_wb_data = S_AXI_WDATA; |
|
|
/////////////////////////////////////////////////// |
// |
// The actual SCOPE |
// |
/////////////////////////////////////////////////// |
// |
// Now that we've finished reading/writing from the |
// bus, ... or at least acknowledging reads and |
// writes from and to the bus--even if they haven't |
// happened yet, now we implement our actual scope. |
// This includes implementing the actual reads/writes |
// from/to the bus. |
// |
// From here on down, is the heart of the scope itself. |
// |
reg [(LGMEM-1):0] raddr; |
reg [(BUSW-1):0] mem[0:((1<<LGMEM)-1)]; |
|
// 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) |
if (write_to_control) |
begin |
br_config <= { i_wb_data[31], |
(i_wb_data[27]), |
i_wb_data[26], |
i_wb_data[19: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]; |
|
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_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 S_AXI_ACLK) |
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 S_AXI_ACLK clock. |
reg dr_triggered, dr_primed; |
wire dw_trigger; |
assign dw_trigger = (dr_primed)&&( |
((i_trigger)&&(~dw_disable_trigger)) |
||(dr_triggered) |
||(dw_manual_trigger)); |
initial dr_triggered = 1'b0; |
always @(posedge i_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 |
reg dr_stopped; |
reg [19:0] counter; // This is unsigned |
initial dr_stopped = 1'b0; |
initial counter = 20'h0000; |
always @(posedge i_clk) |
if (dw_reset) |
begin |
counter <= 0; |
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 |
|
// |
// 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_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))) |
begin |
// mem[waddr] <= i_data; |
waddr <= waddr + {{(LGMEM-1){1'b0}},1'b1}; |
dr_primed <= (dr_primed)||(&waddr); |
end |
always @(posedge i_clk) |
if ((i_ce)&&((~dr_triggered)||(counter < bw_holdoff))) |
mem[waddr] <= i_data; |
|
// |
// Clock transfer of the status signals |
// |
wire bw_stopped, bw_triggered, bw_primed; |
generate |
if (SYNCHRONOUS > 0) |
begin |
assign bw_stopped = dr_stopped; |
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 S_AXI_ACLK) |
if (bw_reset_request) |
begin |
q_oflags <= 3'h0; |
r_oflags <= 3'h0; |
end else begin |
q_oflags <= { dr_stopped, 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 |
|
// Reads use the bus clock |
reg br_wb_ack; |
initial br_wb_ack = 1'b0; |
always @(posedge S_AXI_ACLK) |
begin |
if ((bw_reset_request)||((write_stb)&&(axi_awaddr[0]))) |
raddr <= 0; |
else if ((read_from_data)&&(bw_stopped)) |
// Data read ... only takes place when stopped |
raddr <= raddr + {{(LGMEM-1){1'b0}},1'b1}; |
end |
|
|
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}} )]; |
|
|
|
|
|
|
|
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, |
bw_stopped, |
bw_triggered, |
bw_primed, |
bw_manual_trigger, |
bw_disable_trigger, |
(raddr == {(LGMEM){1'b0}}), |
bw_lgmem, |
bw_holdoff }; |
else if (~bw_stopped) // read, prior to stopping |
axi_rdata <= i_data; |
else // if (i_wb_addr) // Read from FIFO memory |
axi_rdata <= nxt_mem; // mem[raddr+waddr]; |
assign S_AXI_RDATA = axi_rdata; |
|
|
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) |
if ((bw_reset_complete)||(bw_reset_request)) |
br_level_interrupt<= 1'b0; |
else |
br_level_interrupt<= (bw_stopped)&&(~bw_disable_trigger); |
|
endmodule |
|
|
/trunk/rtl/wbscopc.v
1,8 → 1,8
/////////////////////////////////////////////////////////////////////////// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: wbscopc.v |
// |
// Project: FPGA Library of Routines |
// 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 |
61,9 → 61,9
// Creator: Dan Gisselquist, Ph.D. |
// Gisselquist Technology, LLC |
// |
/////////////////////////////////////////////////////////////////////////// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// 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 |
76,7 → 76,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. |
// |
84,7 → 84,7
// http://www.gnu.org/licenses/gpl.html |
// |
// |
///////////////////////////////////////////////////////////////////////////// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
module wbscopc(i_clk, i_ce, i_trigger, i_data, |
91,7 → 91,7
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=32, BUSW = 32, SYNCHRONOUS=1; |
parameter LGMEM = 5'd10, NELM=31, BUSW = 32, SYNCHRONOUS=1; |
// The input signals that we wish to record |
input i_clk, i_ce, i_trigger; |
input [(NELM-1):0] i_data; |
113,7 → 113,7
// When is the full scope reset? Capture that reset bit from any |
// write. |
wire lcl_reset; |
assign lcl_reset = (i_wb_cyc)&&(i_wb_stb)&&(~i_wb_addr)&&(i_wb_we) |
assign lcl_reset = (i_wb_stb)&&(~i_wb_addr)&&(i_wb_we) |
&&(~i_wb_data[31]); |
|
// A big part of this scope is the 'address' of any particular |
138,6 → 138,14
else |
ck_addr <= ck_addr + 1; |
|
wire [(BUSW-2):0] w_data; |
generate |
if (NELM == BUSW-1) |
assign w_data = i_data; |
else |
assign w_data = { {(BUSW-NELM-1){1'b0}}, i_data }; |
endgenerate |
|
// |
// To do our compression, we keep track of two registers: the most |
// recent data to the device (imm_ prefix) and the data from one |
159,7 → 167,7
lst_dat <= 0; |
end else if ((i_ce)&&(i_data != lst_dat)) |
begin |
imm_val <= { {(BUSW-1-NELM){1'b0}}, i_data }; |
imm_val <= w_data; |
imm_adr <= 1'b0; |
lst_val <= imm_val; |
lst_adr <= imm_adr; |
/trunk/rtl/wbscope.v
1,22 → 1,21
/////////////////////////////////////////////////////////////////////////// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: wbscope.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. The general operation is such that this 'scope' can |
// record and report on any 32 bit value transiting through the |
// FPGA. Once started and 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 from the |
// buffer, oldest to most recent. After reading, the scope may |
// then be reset for another run. |
// 'scope' or (perhaps more appropriately) a bus accessed logic analyzer. |
// The general operation is such that this 'scope' can record and report |
// on any 32 bit value transiting through the FPGA. Once started and |
// 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 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. |
// |
// In general, therefore, operation happens in this fashion: |
// In general, therefore, operation happens in this fashion: |
// 1. A reset is issued. |
// 2. Recording starts, in a circular buffer, and continues until |
// 3. The trigger line is asserted. |
58,9 → 57,9
// Creator: Dan Gisselquist, Ph.D. |
// Gisselquist Technology, LLC |
// |
/////////////////////////////////////////////////////////////////////////// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// 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 |
73,7 → 72,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. |
// |
81,12 → 80,18
// http://www.gnu.org/licenses/gpl.html |
// |
// |
///////////////////////////////////////////////////////////////////////////// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
module wbscope(i_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, BUSW = 32, SYNCHRONOUS=1; |
parameter [4:0] LGMEM = 5'd10; |
parameter BUSW = 32; |
parameter [0:0] SYNCHRONOUS=1; |
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; |
107,23 → 112,25
// 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 = ((1<<(LGMEM-1))-4); |
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_cyc)&&(i_wb_stb)&&(~i_wb_addr)) |
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], |
i_wb_data[19:0] }; |
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[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 |
135,7 → 142,8
assign bw_reset_complete = bw_reset_request; |
end else begin |
reg r_reset_complete; |
reg [2:0] r_iflags, q_iflags; |
(* 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 |
152,7 → 160,8
assign dw_manual_trigger = r_iflags[1]; |
assign dw_disable_trigger = r_iflags[0]; |
|
reg q_reset_complete, qq_reset_complete; |
(* 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; |
175,8 → 184,7
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) |
189,24 → 197,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; |
initial counter = 0; |
always @(posedge i_clk) |
if (dw_reset) |
begin |
counter <= 0; |
dr_stopped <= 1'b0; |
end else if ((i_ce)&&(dr_triggered)) |
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. |
if (counter < bw_holdoff) |
counter <= counter + 20'h01; |
else |
dr_stopped <= 1'b1; |
counter <= counter + 1'b1; |
end |
always @(posedge i_clk) |
if ((!dr_triggered)||(dw_reset)) |
dr_stopped <= 1'b0; |
else |
dr_stopped <= (counter >= br_holdoff); |
|
// |
// Actually do our writes to memory. Record, via 'primed' when |
226,7 → 236,7
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}; |
233,7 → 243,7
dr_primed <= (dr_primed)||(&waddr); |
end |
always @(posedge i_clk) |
if ((i_ce)&&((~dr_triggered)||(counter < bw_holdoff))) |
if ((i_ce)&&(!dr_stopped)) |
mem[waddr] <= i_data; |
|
// |
252,7 → 262,8
// for many clocks. Swapping is thus easy--two flip flops to |
// protect against meta-stability and we're done. |
// |
reg [2:0] q_oflags, r_oflags; |
(* 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) |
274,16 → 285,16
reg br_wb_ack; |
initial br_wb_ack = 1'b0; |
wire bw_cyc_stb; |
assign bw_cyc_stb = ((i_wb_cyc)&&(i_wb_stb)); |
assign bw_cyc_stb = (i_wb_stb); |
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 + {{(LGMEM-1){1'b0}},1'b1}; // Data read, when stopped |
else if ((bw_cyc_stb)&&(i_wb_addr)&&(!i_wb_we)&&(bw_stopped)) |
raddr <= raddr + 1'b1; // Data read, when stopped |
|
if ((bw_cyc_stb)&&(~i_wb_we)) |
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)) |
296,13 → 307,19
reg [31:0] nxt_mem; |
always @(posedge i_wb_clk) |
nxt_mem <= mem[raddr+waddr+ |
(((bw_cyc_stb)&&(i_wb_addr)&&(~i_wb_we)) ? |
(((bw_cyc_stb)&&(i_wb_addr)&&(!i_wb_we)) ? |
{{(LGMEM-1){1'b0}},1'b1} : { (LGMEM){1'b0}} )]; |
|
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 |
if (!i_wb_addr) // Control register read |
o_wb_data <= { bw_reset_request, |
bw_stopped, |
bw_triggered, |
311,8 → 328,8
bw_disable_trigger, |
(raddr == {(LGMEM){1'b0}}), |
bw_lgmem, |
bw_holdoff }; |
else if (~bw_stopped) // read, prior to stopping |
full_holdoff }; |
else if (!bw_stopped) // read, prior to stopping |
o_wb_data <= i_data; |
else // if (i_wb_addr) // Read from FIFO memory |
o_wb_data <= nxt_mem; // mem[raddr+waddr]; |
322,12 → 339,12
|
reg br_level_interrupt; |
initial br_level_interrupt = 1'b0; |
assign o_interrupt = (bw_stopped)&&(~bw_disable_trigger) |
&&(~br_level_interrupt); |
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); |
br_level_interrupt<= (bw_stopped)&&(!bw_disable_trigger); |
|
endmodule |
trunk/rtl
Property changes :
Added: svn:ignore
## -0,0 +1 ##
+obj_dir
Index: trunk/sw/cfgscope.cpp
===================================================================
--- trunk/sw/cfgscope.cpp (revision 11)
+++ trunk/sw/cfgscope.cpp (revision 12)
@@ -1,31 +1,30 @@
-///////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
//
// Filename: cfgscope.cpp
//
-// Project: FPGA library development (Basys-3 development board)
+// Project: WBScope, a wishbone hosted scope
//
// Purpose: To read out, and decompose, the results of the wishbone scope
// as applied to the ICAPE2 interaction.
//
-// This is provided together with the wbscope project as an
-// example of what might be done with the wishbone scope.
-// The intermediate details, though, between this and the
-// wishbone scope are not part of the wishbone scope project.
+// This is provided together with the wbscope project as an example of
+// what might be done with the wishbone scope. The intermediate details,
+// though, between this and the wishbone scope are not part of the
+// wishbone scope project.
//
-// Using this particular scope made it a *lot* easier to get the
-// ICAPE2 interface up and running, since I was able to see what
-// was going right (or wrong) with the interface as I was
-// developing it. Sure, it would've been better to get it to work
-// under a simulator instead of with the scope, but not being
-// certain of how the interface was supposed to work made building
-// a simulator difficult.
+// Using this particular scope made it a *lot* easier to get the ICAPE2
+// interface up and running, since I was able to see what was going right
+// (or wrong) with the interface as I was developing it. Sure, it
+// would've been better to get it to work under a simulator instead of
+// with the scope, but not being certain of how the interface was
+// supposed to work made building a simulator difficult.
//
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
//
-///////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
//
-// 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
@@ -38,7 +37,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
// for a copy.
//
@@ -46,36 +45,76 @@
// http://www.gnu.org/licenses/gpl.html
//
//
-///////////////////////////////////////////////////////////////////////////
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+////////////////////////////////////////////////////////////////////////////////
+//
+//
+#include "devbus.h"
+#include "scopecls.h"
-#include "port.h"
-#include "llcomms.h" // This defines how we talk to the device over wishbone
-#include "regdefs.h"
+//
+// CFGSCOPE
+//
+// When you wish to build your own scope, you'll need to build a version of this
+// class to do so. This class has two particular functions to it: one
+// (optional) one to define the traces used incase we wish to split these apart
+// for output to a VCD file. The other function is for use with debug-by-printf
+// approaches. As a result, it provides for a more flexible (textual) output.
+//
+class CFGSCOPE : public SCOPE {
-// Here are the two registers needed for accessing our scope: A control register
-// and a data register.
-#define WBSCOPE R_CFGSCOPE
-#define WBSCOPEDATA R_CFGSCOPED
+ virtual void define_traces(void) {
+ // Heres the interface for VCD files: We need to tell the VCD
+ // writer the names of all of our traces, how many bits each
+ // trace uses, and where the location of the value exists within
+ // the 32-bit trace word.
+ register_trace("cs_n", 1, 31);
+ register_trace("we_n", 1, 30);
+ register_trace("code", 6, 24);
+ register_trace("value", 24, 0);
+ }
-//
-// The DEVBUS structure encapsulates wishbone accesses, so that this code can
-// access the wishbone bus on the FPGA.
-DEVBUS *m_fpga;
-void closeup(int v) {
- m_fpga->kill();
- exit(0);
-}
+ //
+ // decode
+ //
+ // Decode the value to the standard-output stream. How you decode this
+ // value is up to you. Prior to the value being printed, a prefix
+ // identifying the clock number (as counted by the scope, with the
+ // internal clock enable on), and the raw value will be printed out.
+ // Further, after doing whatever printing you wish to do here, a newline
+ // will be printed before going to the next value.
+ //
+ virtual void decode(DEVBUS::BUSW v) const {
+ // Now, let's decompose our 32-bit wires into something ...
+ // meaningful and dump it to stdout. This section will change
+ // from project to project, scope to scope, depending on what
+ // wires are placed into the scope.
+ printf("%s %s ", (v&0x80000000)?" ":"CS",
+ (v&0x40000000)?"RD":"WR");
+ unsigned cw = (v>>24)&0x03f;
+ switch(cw) {
+ case 0x20: printf("DUMMY"); break;
+ case 0x10: printf("NOOP "); break;
+ case 0x08: printf("SYNC "); break;
+ case 0x04: printf("CMD "); break;
+ case 0x02: printf("IPROG"); break;
+ case 0x01: printf("DSYNC"); break;
+ default: printf("OTHER"); break;
+ }
+ printf(" -> %02x", v & 0x0ffffff);
+ }
+};
+
int main(int argc, char **argv) {
+ // The DEVBUS structure encapsulates wishbone accesses, so that this
+ // code can access the wishbone bus on the FPGA.
+ DEVBUS *m_fpga;
+
// Open up a port to talk to the FPGA ...
+ //
+ // This may be unique to your FPGA, so feel free to adjust these lines
+ // for your setup. The result, though, must be a DEVBUS structure
+ // giving you access to the FPGA.
#ifndef FORCE_UART
m_fpga = new FPGA(new NETCOMMS("lazarus",PORT));
#else
@@ -82,75 +121,28 @@
m_fpga = new FPGA(new TTYCOMMS("/dev/ttyUSB2"));
#endif
- signal(SIGSTOP, closeup);
- signal(SIGHUP, closeup);
+ //
+ CFGSCOPE *scope = new CFGSCOPE(m_fpga, WBSCOPE);
// Check to see whether or not the scope has captured the data we need
- // yet or not. If not, exit kindly.
- unsigned v, lgln, scoplen;
- v = m_fpga->readio(WBSCOPE);
- if (0x60000000 != (v & 0x60000000)) {
- printf("Scope is not yet ready:\n");
- printf("\tRESET:\t\t%s\n", (v&0x80000000)?"Ongoing":"Complete");
- printf("\tSTOPPED:\t%s\n", (v&0x40000000)?"Yes":"No");
- printf("\tTRIGGERED:\t%s\n", (v&0x20000000)?"Yes":"No");
- printf("\tPRIMED:\t\t%s\n", (v&0x10000000)?"Yes":"No");
- printf("\tMANUAL:\t\t%s\n", (v&0x08000000)?"Yes":"No");
- printf("\tDISABLED:\t%s\n", (v&0x04000000)?"Yes":"No");
- printf("\tZERO:\t\t%s\n", (v&0x02000000)?"Yes":"No");
- exit(0);
- }
+ // yet or not.
+ if (scope->ready()) {
+ // If the data has been captured, we call print(). This
+ // function will print all our values to the standard output,
+ // and it will call the decode() function above to do it.
+ scope->print();
- // Since the length of the scope memory is a configuration parameter
- // internal to the scope, we read it here to find out how it was
- // configured.
- lgln = (v>>20) & 0x1f;
- scoplen = (1<readz(WBSCOPEDATA, scoplen, buf);
-
- printf("Vector read complete\n");
+ // You can also write the results to a VCD trace file. To do
+ // this, just call writevcd and pass it the name you wish your
+ // VCD file to have.
+ scope->writevcd("cfgtrace.vcd");
} else {
- for(int i=0; ireadio(WBSCOPEDATA);
+ // If the scope isnt yet ready, print a message, decode its
+ // current state, and exit kindly.
+ printf("Scope is not (yet) ready:\n");
+ scope->decode_control();
}
- // Now, let's decompose our 32-bit wires into something ... meaningful.
- // This section will change from project to project, scope to scope,
- // depending on what wires are placed into the scope.
- for(int i=0; i0)&&(buf[i] == buf[i-1])&&
- (i>24)&0x03f;
- switch(cw) {
- case 0x20: printf("DUMMY"); break;
- case 0x10: printf("NOOP "); break;
- case 0x08: printf("SYNC "); break;
- case 0x04: printf("CMD "); break;
- case 0x02: printf("IPROG"); break;
- case 0x01: printf("DSYNC"); break;
- default: printf("OTHER"); break;
- }
- printf(" -> %02x\n", buf[i] & 0x0ffffff);
- }
-
// Clean up our interface, now, and we're done.
delete m_fpga;
}
-
/trunk/sw/devbus.h
0,0 → 1,103
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: devbus.h |
// |
// Project: OpenArty, an entirely open SoC based upon the Arty platform |
// |
// Purpose: The purpose of this file is to document an interface which |
// any devic 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. |
// |
// |
// 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 |
// |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
#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 |
virtual void writeio(const BUSW a, const BUSW v) = 0; |
|
// Read a single value to a single address |
virtual BUSW readio(const BUSW a) = 0; |
|
// Read a series of values from values from a block of memory |
virtual void readi(const BUSW a, const int len, BUSW *buf) = 0; |
|
// Read a series of values from the same address in memory |
virtual void readz(const BUSW a, const int len, BUSW *buf) = 0; |
|
virtual void writei(const BUSW a, const int len, const BUSW *buf) = 0; |
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/sw/scopecls.cpp
0,0 → 1,283
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: scopecls.cpp |
// |
// Project: WBScope, a wishbone hosted scope |
// |
// 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. |
// |
// 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 <unistd.h> |
#include <strings.h> |
#include <ctype.h> |
#include <string.h> |
#include <signal.h> |
#include <assert.h> |
#include <time.h> |
|
#include "devbus.h" |
#include "scopecls.h" |
|
bool SCOPE::ready() { |
unsigned v; |
v = m_fpga->readio(m_addr); |
if (m_scoplen == 0) { |
m_scoplen = (1<<((v>>20)&0x01f)); |
} v = (v>>28)&6; |
return (v==6); |
} |
|
void SCOPE::decode_control(void) { |
unsigned v; |
|
v = m_fpga->readio(m_addr); |
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"); |
printf("\t28. PRIMED:\t%s\n", (v&0x10000000)?"Yes":"No"); |
printf("\t27. MANUAL:\t%s\n", (v&0x08000000)?"Yes":"No"); |
printf("\t26. DISABLED:\t%s\n", (v&0x04000000)?"Yes":"No"); |
printf("\t25. ZERO:\t%s\n", (v&0x02000000)?"Yes":"No"); |
printf("\tSCOPLEN:\t%08x (%d)\n", m_scoplen, m_scoplen); |
printf("\tHOLDOFF:\t%08x\n", (v&0x0fffff)); |
printf("\tTRIGLOC:\t%d\n", m_scoplen-(v&0x0fffff)); |
} |
|
int SCOPE::scoplen(void) { |
unsigned v, lgln; |
|
// If the scope length is zero, then the scope isn't present. |
// We use a length of zero here to also represent whether or not we've |
// looked up the length by reading from the scope. |
if (m_scoplen == 0) { |
v = m_fpga->readio(m_addr); |
|
// Since the length of the scope memory is a configuration |
// parameter internal to the scope, we read it here to find |
// out how the scope was configured. |
lgln = (v>>20) & 0x1f; |
|
// If the length is still zero, then there is no scope installed |
if (lgln != 0) { |
// Otherwise, the scope length contained in the device |
// control register is the log base 2 of the actual |
// length of what's in the FPGA. Here, we just convert |
// 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 |
// slow down to read that length from the device a second time. |
} return m_scoplen; |
} |
|
// |
// rawread |
// |
// Read the scope data from the scope. |
void SCOPE::rawread(void) { |
// If we've already read the data from the scope, then we don't need |
// to read it a second time. |
if (m_data) |
return; |
|
// Let's get the length of the scope, and check that it is a valid |
// length |
if (scoplen() <= 4) { |
printf("ERR: Scope has less than a minimum length. Is it truly a scope?\n"); |
return; |
} |
|
// Now that we know the size of the scopes buffer, let's allocate a |
// buffer to hold all this data |
m_data = new DEVBUS::BUSW[m_scoplen]; |
|
// There are two means of reading from a DEVBUS interface: The first |
// is a vector read, optimized so that the address and read command |
// only needs to be sent once. This is the optimal means. However, |
// if the bus isn't (yet) trustworthy, it may be more reliable to access |
// the port by reading one register at a time--hence the second method. |
// If the bus works, you'll want to use readz(): read scoplen values |
// into the buffer, from the address WBSCOPEDATA, without incrementing |
// the address each time (hence the 'z' in readz--for zero increment). |
if (m_vector_read) { |
m_fpga->readz(m_addr+4, m_scoplen, m_data); |
} else { |
for(unsigned int i=0; i<m_scoplen; i++) |
m_data[i] = m_fpga->readio(m_addr+4); |
} |
} |
|
void SCOPE::print(void) { |
DEVBUS::BUSW addrv = 0; |
|
rawread(); |
|
if(m_compressed) { |
for(int i=0; i<(int)m_scoplen; i++) { |
if ((m_data[i]>>31)&1) { |
addrv += (m_data[i]&0x7fffffff); |
printf(" ** (+0x%08x = %8d)\n", |
(m_data[i]&0x07fffffff), |
(m_data[i]&0x07fffffff)); |
continue; |
} |
printf("%10d %08x: ", addrv++, m_data[i]); |
decode(m_data[i]); |
printf("\n"); |
} |
} else { |
for(int i=0; i<(int)m_scoplen; i++) { |
if ((i>0)&&(m_data[i] == m_data[i-1])&&(i<(int)(m_scoplen-1))) { |
if ((i>2)&&(m_data[i] != m_data[i-2])) |
printf(" **** ****\n"); |
continue; |
} printf("%9d %08x: ", i, m_data[i]); |
decode(m_data[i]); |
printf("\n"); |
} |
} |
} |
|
void SCOPE::write_trace_timescale(FILE *fp) { |
fprintf(fp, "$timescle 1ns $end\n\n"); |
} |
|
// $dumpoff and $dumpon |
void SCOPE::write_trace_header(FILE *fp) { |
time_t now; |
|
time(&now); |
fprintf(fp, "$version Generated by WBScope $end\n"); |
fprintf(fp, "$date %s\n $end\n", ctime(&now)); |
write_trace_timescale(fp); |
|
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); |
|
for(unsigned i=0; i<m_traces.size(); i++) { |
TRACEINFO *info = m_traces[i]; |
fprintf(fp, " $var wire %2d %s %s", |
info->m_nbits, info->m_key, info->m_name); |
if ((info->m_nbits > 0)&&(NULL == strchr(info->m_name, '['))) |
fprintf(fp, "[%d:0] $end\n", info->m_nbits-1); |
else |
fprintf(fp, " $end\n"); |
} |
|
fprintf(fp, " $upscope $end\n"); |
fprintf(fp, "$enddefinitions $end\n"); |
} |
|
void SCOPE::write_binary_trace(FILE *fp, const int nbits, unsigned val, |
const char *str) { |
if (nbits <= 1) { |
fprintf(fp, "%d%s\n", val&1, str); |
return; |
} |
if ((unsigned)nbits < sizeof(val)*8) |
val &= ~(-1<<nbits); |
fputs("b", fp); |
for(int i=0; i<nbits; i++) |
fprintf(fp, "%d", (val>>(nbits-1-i))&1); |
fprintf(fp, " %s\n", str); |
} |
|
void SCOPE::write_binary_trace(FILE *fp, TRACEINFO *info, unsigned value) { |
write_binary_trace(fp, info->m_nbits, (value>>info->m_nshift), |
info->m_key); |
} |
|
void SCOPE::register_trace(const char *name, |
unsigned nbits, unsigned shift) { |
TRACEINFO *info = new TRACEINFO; |
int nkey = m_traces.size(); |
|
info->m_name = name; |
info->m_nbits = nbits; |
info->m_nshift = shift; |
|
info->m_key[0] = 'v'; |
if (nkey < 26) |
info->m_key[1] = 'a'+nkey; |
else if (nkey < 26+26) |
info->m_key[1] = 'A'+nkey-26; |
else // if (nkey < 26+26+10) // Should never happen |
info->m_key[1] = '0'+nkey-26-26; |
info->m_key[2] = '\0'; |
info->m_key[3] = '\0'; |
|
m_traces.push_back(info); |
} |
|
void SCOPE::define_traces(void) {} |
|
void SCOPE::writevcd(const char *trace_file_name) { |
FILE *fp = fopen(trace_file_name, "w"); |
|
if (fp == NULL) { |
fprintf(stderr, "ERR: Cannot open %s for writing!\n", trace_file_name); |
fprintf(stderr, "ERR: Trace file not written\n"); |
return; |
} |
|
if (!m_data) |
rawread(); |
|
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"); |
} |
} |
|
/trunk/sw/scopecls.h
0,0 → 1,88
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: scopecls.h |
// |
// Project: WBScope, a wishbone hosted scope |
// |
// 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. |
// |
// 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 SCOPECLS_H |
#define SCOPECLS_H |
|
#include <vector> |
#include "devbus.h" |
|
class TRACEINFO { |
public: |
const char *m_name; |
char m_key[4]; |
unsigned m_nbits, m_nshift; |
}; |
|
class SCOPE { |
DEVBUS *m_fpga; |
DEVBUS::BUSW m_addr; |
bool m_compressed, m_vector_read; |
unsigned m_scoplen; |
unsigned *m_data; |
std::vector<TRACEINFO *> m_traces; |
|
public: |
SCOPE(DEVBUS *fpga, unsigned addr, |
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; } |
|
bool ready(); |
void decode_control(void); |
int scoplen(void); |
virtual void rawread(void); |
void print(void); |
virtual void write_trace_timescale(FILE *fp); |
virtual void write_trace_header(FILE *fp); |
void write_binary_trace(FILE *fp, const int nbits, |
unsigned val, const char *str); |
void write_binary_trace(FILE *fp, TRACEINFO *info, |
unsigned value); |
void writevcd(const char *trace_file_name); |
void register_trace(const char *varname, |
unsigned nbits, unsigned shift); |
virtual void decode(DEVBUS::BUSW v) const = 0; |
virtual void define_traces(void); |
}; |
|
#endif // SCOPECLS_H |
/trunk/wbscope.core
0,0 → 1,15
CAPI=1 |
[main] |
description = Pipelined Wishbone to AXI converter |
|
[fileset rtl] |
files = |
rtl/wbscope.v |
rtl/wbscopc.v |
file_type = verilogSource |
|
[provider] |
name=github |
user=ZipCPU |
repo=wbscope |
version = master |
trunk
Property changes :
Added: svn:ignore
## -0,0 +1,2 ##
+.git
+.gitignore