| 1 |
30 |
dgisselq |
////////////////////////////////////////////////////////////////////////////////
|
| 2 |
3 |
dgisselq |
//
|
| 3 |
|
|
// Filename: wbscope.v
|
| 4 |
|
|
//
|
| 5 |
50 |
dgisselq |
// Project: WBScope, a wishbone hosted scope
|
| 6 |
3 |
dgisselq |
//
|
| 7 |
|
|
// Purpose: This is a generic/library routine for providing a bus accessed
|
| 8 |
50 |
dgisselq |
// 'scope' or (perhaps more appropriately) a bus accessed logic analyzer.
|
| 9 |
|
|
// The general operation is such that this 'scope' can record and report
|
| 10 |
|
|
// on any 32 bit value transiting through the FPGA. Once started and
|
| 11 |
|
|
// reset, the scope records a copy of the input data every time the clock
|
| 12 |
|
|
// ticks with the circuit enabled. That is, it records these values up
|
| 13 |
|
|
// until the trigger. Once the trigger goes high, the scope will record
|
| 14 |
|
|
// for bw_holdoff more counts before stopping. Values may then be read
|
| 15 |
|
|
// from the buffer, oldest to most recent. After reading, the scope may
|
| 16 |
|
|
// then be reset for another run.
|
| 17 |
3 |
dgisselq |
//
|
| 18 |
50 |
dgisselq |
// In general, therefore, operation happens in this fashion:
|
| 19 |
3 |
dgisselq |
// 1. A reset is issued.
|
| 20 |
|
|
// 2. Recording starts, in a circular buffer, and continues until
|
| 21 |
|
|
// 3. The trigger line is asserted.
|
| 22 |
|
|
// The scope registers the asserted trigger by setting
|
| 23 |
|
|
// the 'o_triggered' output flag.
|
| 24 |
|
|
// 4. A counter then ticks until the last value is written
|
| 25 |
|
|
// The scope registers that it has stopped recording by
|
| 26 |
|
|
// setting the 'o_stopped' output flag.
|
| 27 |
|
|
// 5. The scope recording is then paused until the next reset.
|
| 28 |
|
|
// 6. While stopped, the CPU can read the data from the scope
|
| 29 |
|
|
// 7. -- oldest to most recent
|
| 30 |
|
|
// 8. -- one value per i_rd&i_clk
|
| 31 |
|
|
// 9. Writes to the data register reset the address to the
|
| 32 |
|
|
// beginning of the buffer
|
| 33 |
|
|
//
|
| 34 |
|
|
// Although the data width DW is parameterized, it is not very changable,
|
| 35 |
|
|
// since the width is tied to the width of the data bus, as is the
|
| 36 |
|
|
// control word. Therefore changing the data width would require changing
|
| 37 |
|
|
// the interface. It's doable, but it would be a change to the interface.
|
| 38 |
|
|
//
|
| 39 |
|
|
// The SYNCHRONOUS parameter turns on and off meta-stability
|
| 40 |
|
|
// synchronization. Ideally a wishbone scope able to handle one or two
|
| 41 |
|
|
// clocks would have a changing number of ports as this SYNCHRONOUS
|
| 42 |
|
|
// parameter changed. Other than running another script to modify
|
| 43 |
|
|
// this, I don't know how to do that so ... we'll just leave it running
|
| 44 |
|
|
// off of two clocks or not.
|
| 45 |
|
|
//
|
| 46 |
|
|
//
|
| 47 |
|
|
// Internal to this routine, registers and wires are named with one of the
|
| 48 |
|
|
// following prefixes:
|
| 49 |
|
|
//
|
| 50 |
|
|
// i_ An input port to the routine
|
| 51 |
|
|
// o_ An output port of the routine
|
| 52 |
|
|
// br_ A register, controlled by the bus clock
|
| 53 |
|
|
// dr_ A register, controlled by the data clock
|
| 54 |
|
|
// bw_ A wire/net, controlled by the bus clock
|
| 55 |
|
|
// dw_ A wire/net, controlled by the data clock
|
| 56 |
|
|
//
|
| 57 |
|
|
// Creator: Dan Gisselquist, Ph.D.
|
| 58 |
|
|
// Gisselquist Technology, LLC
|
| 59 |
|
|
//
|
| 60 |
30 |
dgisselq |
////////////////////////////////////////////////////////////////////////////////
|
| 61 |
3 |
dgisselq |
//
|
| 62 |
50 |
dgisselq |
// Copyright (C) 2015-2017, Gisselquist Technology, LLC
|
| 63 |
3 |
dgisselq |
//
|
| 64 |
|
|
// This program is free software (firmware): you can redistribute it and/or
|
| 65 |
|
|
// modify it under the terms of the GNU General Public License as published
|
| 66 |
|
|
// by the Free Software Foundation, either version 3 of the License, or (at
|
| 67 |
|
|
// your option) any later version.
|
| 68 |
|
|
//
|
| 69 |
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
| 70 |
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
|
| 71 |
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
| 72 |
|
|
// for more details.
|
| 73 |
|
|
//
|
| 74 |
|
|
// You should have received a copy of the GNU General Public License along
|
| 75 |
50 |
dgisselq |
// with this program. (It's in the $(ROOT)/doc directory. Run make with no
|
| 76 |
3 |
dgisselq |
// target there if the PDF file isn't present.) If not, see
|
| 77 |
|
|
// <http://www.gnu.org/licenses/> for a copy.
|
| 78 |
|
|
//
|
| 79 |
|
|
// License: GPL, v3, as defined and found on www.gnu.org,
|
| 80 |
|
|
// http://www.gnu.org/licenses/gpl.html
|
| 81 |
|
|
//
|
| 82 |
|
|
//
|
| 83 |
30 |
dgisselq |
////////////////////////////////////////////////////////////////////////////////
|
| 84 |
|
|
//
|
| 85 |
|
|
//
|
| 86 |
3 |
dgisselq |
module wbscope(i_clk, i_ce, i_trigger, i_data,
|
| 87 |
|
|
i_wb_clk, i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data,
|
| 88 |
|
|
o_wb_ack, o_wb_stall, o_wb_data,
|
| 89 |
|
|
o_interrupt);
|
| 90 |
30 |
dgisselq |
parameter LGMEM = 5'd10, BUSW = 32, SYNCHRONOUS=1,
|
| 91 |
|
|
DEFAULT_HOLDOFF = ((1<<(LGMEM-1))-4);
|
| 92 |
3 |
dgisselq |
// The input signals that we wish to record
|
| 93 |
|
|
input i_clk, i_ce, i_trigger;
|
| 94 |
|
|
input [(BUSW-1):0] i_data;
|
| 95 |
|
|
// The WISHBONE bus for reading and configuring this scope
|
| 96 |
|
|
input i_wb_clk, i_wb_cyc, i_wb_stb, i_wb_we;
|
| 97 |
|
|
input i_wb_addr; // One address line only
|
| 98 |
|
|
input [(BUSW-1):0] i_wb_data;
|
| 99 |
|
|
output wire o_wb_ack, o_wb_stall;
|
| 100 |
|
|
output reg [(BUSW-1):0] o_wb_data;
|
| 101 |
|
|
// And, finally, for a final flair --- offer to interrupt the CPU after
|
| 102 |
|
|
// our trigger has gone off. This line is equivalent to the scope
|
| 103 |
|
|
// being stopped. It is not maskable here.
|
| 104 |
|
|
output wire o_interrupt;
|
| 105 |
|
|
|
| 106 |
|
|
reg [(LGMEM-1):0] raddr;
|
| 107 |
|
|
reg [(BUSW-1):0] mem[0:((1<<LGMEM)-1)];
|
| 108 |
|
|
|
| 109 |
|
|
// Our status/config register
|
| 110 |
|
|
wire bw_reset_request, bw_manual_trigger,
|
| 111 |
|
|
bw_disable_trigger, bw_reset_complete;
|
| 112 |
|
|
reg [22:0] br_config;
|
| 113 |
|
|
wire [19:0] bw_holdoff;
|
| 114 |
30 |
dgisselq |
initial br_config = DEFAULT_HOLDOFF;
|
| 115 |
3 |
dgisselq |
always @(posedge i_wb_clk)
|
| 116 |
50 |
dgisselq |
if ((i_wb_stb)&&(~i_wb_addr))
|
| 117 |
3 |
dgisselq |
begin
|
| 118 |
25 |
dgisselq |
if (i_wb_we)
|
| 119 |
|
|
br_config <= { i_wb_data[31],
|
| 120 |
|
|
(i_wb_data[27]),
|
| 121 |
|
|
i_wb_data[26],
|
| 122 |
|
|
i_wb_data[19:0] };
|
| 123 |
3 |
dgisselq |
end else if (bw_reset_complete)
|
| 124 |
|
|
br_config[22] <= 1'b1;
|
| 125 |
|
|
assign bw_reset_request = (~br_config[22]);
|
| 126 |
|
|
assign bw_manual_trigger = (br_config[21]);
|
| 127 |
|
|
assign bw_disable_trigger = (br_config[20]);
|
| 128 |
|
|
assign bw_holdoff = br_config[19:0];
|
| 129 |
|
|
|
| 130 |
|
|
wire dw_reset, dw_manual_trigger, dw_disable_trigger;
|
| 131 |
|
|
generate
|
| 132 |
|
|
if (SYNCHRONOUS > 0)
|
| 133 |
|
|
begin
|
| 134 |
|
|
assign dw_reset = bw_reset_request;
|
| 135 |
|
|
assign dw_manual_trigger = bw_manual_trigger;
|
| 136 |
|
|
assign dw_disable_trigger = bw_disable_trigger;
|
| 137 |
|
|
assign bw_reset_complete = bw_reset_request;
|
| 138 |
|
|
end else begin
|
| 139 |
|
|
reg r_reset_complete;
|
| 140 |
30 |
dgisselq |
(* ASYNC_REG = "TRUE" *) reg [2:0] q_iflags;
|
| 141 |
|
|
reg [2:0] r_iflags;
|
| 142 |
3 |
dgisselq |
|
| 143 |
|
|
// Resets are synchronous to the bus clock, not the data clock
|
| 144 |
|
|
// so do a clock transfer here
|
| 145 |
|
|
initial q_iflags = 3'b000;
|
| 146 |
|
|
initial r_reset_complete = 1'b0;
|
| 147 |
|
|
always @(posedge i_clk)
|
| 148 |
|
|
begin
|
| 149 |
|
|
q_iflags <= { bw_reset_request, bw_manual_trigger, bw_disable_trigger };
|
| 150 |
|
|
r_iflags <= q_iflags;
|
| 151 |
|
|
r_reset_complete <= (dw_reset);
|
| 152 |
|
|
end
|
| 153 |
|
|
|
| 154 |
|
|
assign dw_reset = r_iflags[2];
|
| 155 |
|
|
assign dw_manual_trigger = r_iflags[1];
|
| 156 |
|
|
assign dw_disable_trigger = r_iflags[0];
|
| 157 |
|
|
|
| 158 |
30 |
dgisselq |
(* ASYNC_REG = "TRUE" *) reg q_reset_complete;
|
| 159 |
|
|
reg qq_reset_complete;
|
| 160 |
3 |
dgisselq |
// Pass an acknowledgement back from the data clock to the bus
|
| 161 |
|
|
// clock that the reset has been accomplished
|
| 162 |
|
|
initial q_reset_complete = 1'b0;
|
| 163 |
|
|
initial qq_reset_complete = 1'b0;
|
| 164 |
|
|
always @(posedge i_wb_clk)
|
| 165 |
|
|
begin
|
| 166 |
|
|
q_reset_complete <= r_reset_complete;
|
| 167 |
|
|
qq_reset_complete <= q_reset_complete;
|
| 168 |
|
|
end
|
| 169 |
|
|
|
| 170 |
|
|
assign bw_reset_complete = qq_reset_complete;
|
| 171 |
|
|
end endgenerate
|
| 172 |
|
|
|
| 173 |
|
|
//
|
| 174 |
|
|
// Set up the trigger
|
| 175 |
|
|
//
|
| 176 |
|
|
//
|
| 177 |
|
|
// Write with the i-clk, or input clock. All outputs read with the
|
| 178 |
|
|
// WISHBONE-clk, or i_wb_clk clock.
|
| 179 |
|
|
reg dr_triggered, dr_primed;
|
| 180 |
|
|
wire dw_trigger;
|
| 181 |
|
|
assign dw_trigger = (dr_primed)&&(
|
| 182 |
|
|
((i_trigger)&&(~dw_disable_trigger))
|
| 183 |
|
|
||(dr_triggered)
|
| 184 |
|
|
||(dw_manual_trigger));
|
| 185 |
|
|
initial dr_triggered = 1'b0;
|
| 186 |
|
|
always @(posedge i_clk)
|
| 187 |
|
|
if (dw_reset)
|
| 188 |
|
|
dr_triggered <= 1'b0;
|
| 189 |
|
|
else if ((i_ce)&&(dw_trigger))
|
| 190 |
|
|
dr_triggered <= 1'b1;
|
| 191 |
|
|
|
| 192 |
|
|
//
|
| 193 |
|
|
// Determine when memory is full and capture is complete
|
| 194 |
|
|
//
|
| 195 |
|
|
// Writes take place on the data clock
|
| 196 |
25 |
dgisselq |
reg dr_stopped;
|
| 197 |
30 |
dgisselq |
(* ASYNC_REG="TRUE" *) reg [19:0] counter;// This is unsigned
|
| 198 |
3 |
dgisselq |
initial dr_stopped = 1'b0;
|
| 199 |
|
|
initial counter = 20'h0000;
|
| 200 |
|
|
always @(posedge i_clk)
|
| 201 |
|
|
if (dw_reset)
|
| 202 |
|
|
counter <= 0;
|
| 203 |
50 |
dgisselq |
else if ((i_ce)&&(dr_triggered)&&(~dr_stopped))
|
| 204 |
3 |
dgisselq |
begin // MUST BE a < and not <=, so that we can keep this w/in
|
| 205 |
|
|
// 20 bits. Else we'd need to add a bit to comparison
|
| 206 |
|
|
// here.
|
| 207 |
50 |
dgisselq |
counter <= counter + 20'h01;
|
| 208 |
3 |
dgisselq |
end
|
| 209 |
50 |
dgisselq |
always @(posedge i_clk)
|
| 210 |
|
|
if ((~dr_triggered)||(dw_reset))
|
| 211 |
|
|
dr_stopped <= 1'b0;
|
| 212 |
|
|
else if (i_ce)
|
| 213 |
|
|
dr_stopped <= (counter+20'd1 >= bw_holdoff);
|
| 214 |
|
|
else
|
| 215 |
|
|
dr_stopped <= (counter >= bw_holdoff);
|
| 216 |
3 |
dgisselq |
|
| 217 |
|
|
//
|
| 218 |
|
|
// Actually do our writes to memory. Record, via 'primed' when
|
| 219 |
|
|
// the memory is full.
|
| 220 |
|
|
//
|
| 221 |
|
|
// The 'waddr' address that we are using really crosses two clock
|
| 222 |
|
|
// domains. While writing and changing, it's in the data clock
|
| 223 |
|
|
// domain. Once stopped, it becomes part of the bus clock domain.
|
| 224 |
|
|
// The clock transfer on the stopped line handles the clock
|
| 225 |
|
|
// transfer for these signals.
|
| 226 |
|
|
//
|
| 227 |
|
|
reg [(LGMEM-1):0] waddr;
|
| 228 |
|
|
initial waddr = {(LGMEM){1'b0}};
|
| 229 |
|
|
initial dr_primed = 1'b0;
|
| 230 |
|
|
always @(posedge i_clk)
|
| 231 |
|
|
if (dw_reset) // For simulation purposes, supply a valid value
|
| 232 |
|
|
begin
|
| 233 |
|
|
waddr <= 0; // upon reset.
|
| 234 |
|
|
dr_primed <= 1'b0;
|
| 235 |
50 |
dgisselq |
end else if ((i_ce)&&((~dr_triggered)||(!dr_stopped)))
|
| 236 |
3 |
dgisselq |
begin
|
| 237 |
|
|
// mem[waddr] <= i_data;
|
| 238 |
|
|
waddr <= waddr + {{(LGMEM-1){1'b0}},1'b1};
|
| 239 |
|
|
dr_primed <= (dr_primed)||(&waddr);
|
| 240 |
|
|
end
|
| 241 |
|
|
always @(posedge i_clk)
|
| 242 |
50 |
dgisselq |
if ((i_ce)&&((~dr_triggered)||(!dr_stopped)))
|
| 243 |
3 |
dgisselq |
mem[waddr] <= i_data;
|
| 244 |
|
|
|
| 245 |
|
|
//
|
| 246 |
|
|
// Clock transfer of the status signals
|
| 247 |
|
|
//
|
| 248 |
|
|
wire bw_stopped, bw_triggered, bw_primed;
|
| 249 |
|
|
generate
|
| 250 |
|
|
if (SYNCHRONOUS > 0)
|
| 251 |
|
|
begin
|
| 252 |
|
|
assign bw_stopped = dr_stopped;
|
| 253 |
|
|
assign bw_triggered = dr_triggered;
|
| 254 |
|
|
assign bw_primed = dr_primed;
|
| 255 |
|
|
end else begin
|
| 256 |
|
|
// These aren't a problem, since none of these are strobe
|
| 257 |
|
|
// signals. They goes from low to high, and then stays high
|
| 258 |
|
|
// for many clocks. Swapping is thus easy--two flip flops to
|
| 259 |
|
|
// protect against meta-stability and we're done.
|
| 260 |
|
|
//
|
| 261 |
30 |
dgisselq |
(* ASYNC_REG = "TRUE" *) reg [2:0] q_oflags;
|
| 262 |
|
|
reg [2:0] r_oflags;
|
| 263 |
3 |
dgisselq |
initial q_oflags = 3'h0;
|
| 264 |
|
|
initial r_oflags = 3'h0;
|
| 265 |
|
|
always @(posedge i_wb_clk)
|
| 266 |
|
|
if (bw_reset_request)
|
| 267 |
|
|
begin
|
| 268 |
|
|
q_oflags <= 3'h0;
|
| 269 |
|
|
r_oflags <= 3'h0;
|
| 270 |
|
|
end else begin
|
| 271 |
|
|
q_oflags <= { dr_stopped, dr_triggered, dr_primed };
|
| 272 |
|
|
r_oflags <= q_oflags;
|
| 273 |
|
|
end
|
| 274 |
|
|
|
| 275 |
|
|
assign bw_stopped = r_oflags[2];
|
| 276 |
|
|
assign bw_triggered = r_oflags[1];
|
| 277 |
|
|
assign bw_primed = r_oflags[0];
|
| 278 |
|
|
end endgenerate
|
| 279 |
|
|
|
| 280 |
|
|
// Reads use the bus clock
|
| 281 |
25 |
dgisselq |
reg br_wb_ack;
|
| 282 |
3 |
dgisselq |
initial br_wb_ack = 1'b0;
|
| 283 |
25 |
dgisselq |
wire bw_cyc_stb;
|
| 284 |
50 |
dgisselq |
assign bw_cyc_stb = (i_wb_stb);
|
| 285 |
3 |
dgisselq |
always @(posedge i_wb_clk)
|
| 286 |
25 |
dgisselq |
begin
|
| 287 |
|
|
if ((bw_reset_request)
|
| 288 |
|
|
||((bw_cyc_stb)&&(i_wb_addr)&&(i_wb_we)))
|
| 289 |
3 |
dgisselq |
raddr <= 0;
|
| 290 |
25 |
dgisselq |
else if ((bw_cyc_stb)&&(i_wb_addr)&&(~i_wb_we)&&(bw_stopped))
|
| 291 |
3 |
dgisselq |
raddr <= raddr + {{(LGMEM-1){1'b0}},1'b1}; // Data read, when stopped
|
| 292 |
|
|
|
| 293 |
25 |
dgisselq |
if ((bw_cyc_stb)&&(~i_wb_we))
|
| 294 |
|
|
begin // Read from the bus
|
| 295 |
|
|
br_wb_ack <= 1'b1;
|
| 296 |
|
|
end else if ((bw_cyc_stb)&&(i_wb_we))
|
| 297 |
|
|
// We did this write above
|
| 298 |
|
|
br_wb_ack <= 1'b1;
|
| 299 |
|
|
else // Do nothing if either i_wb_cyc or i_wb_stb are low
|
| 300 |
|
|
br_wb_ack <= 1'b0;
|
| 301 |
3 |
dgisselq |
end
|
| 302 |
|
|
|
| 303 |
|
|
reg [31:0] nxt_mem;
|
| 304 |
|
|
always @(posedge i_wb_clk)
|
| 305 |
25 |
dgisselq |
nxt_mem <= mem[raddr+waddr+
|
| 306 |
|
|
(((bw_cyc_stb)&&(i_wb_addr)&&(~i_wb_we)) ?
|
| 307 |
|
|
{{(LGMEM-1){1'b0}},1'b1} : { (LGMEM){1'b0}} )];
|
| 308 |
3 |
dgisselq |
|
| 309 |
|
|
wire [4:0] bw_lgmem;
|
| 310 |
|
|
assign bw_lgmem = LGMEM;
|
| 311 |
|
|
always @(posedge i_wb_clk)
|
| 312 |
25 |
dgisselq |
if (~i_wb_addr) // Control register read
|
| 313 |
3 |
dgisselq |
o_wb_data <= { bw_reset_request,
|
| 314 |
|
|
bw_stopped,
|
| 315 |
|
|
bw_triggered,
|
| 316 |
|
|
bw_primed,
|
| 317 |
|
|
bw_manual_trigger,
|
| 318 |
|
|
bw_disable_trigger,
|
| 319 |
|
|
(raddr == {(LGMEM){1'b0}}),
|
| 320 |
|
|
bw_lgmem,
|
| 321 |
|
|
bw_holdoff };
|
| 322 |
|
|
else if (~bw_stopped) // read, prior to stopping
|
| 323 |
|
|
o_wb_data <= i_data;
|
| 324 |
|
|
else // if (i_wb_addr) // Read from FIFO memory
|
| 325 |
|
|
o_wb_data <= nxt_mem; // mem[raddr+waddr];
|
| 326 |
|
|
|
| 327 |
|
|
assign o_wb_stall = 1'b0;
|
| 328 |
25 |
dgisselq |
assign o_wb_ack = (i_wb_cyc)&&(br_wb_ack);
|
| 329 |
3 |
dgisselq |
|
| 330 |
|
|
reg br_level_interrupt;
|
| 331 |
|
|
initial br_level_interrupt = 1'b0;
|
| 332 |
|
|
assign o_interrupt = (bw_stopped)&&(~bw_disable_trigger)
|
| 333 |
|
|
&&(~br_level_interrupt);
|
| 334 |
|
|
always @(posedge i_wb_clk)
|
| 335 |
|
|
if ((bw_reset_complete)||(bw_reset_request))
|
| 336 |
|
|
br_level_interrupt<= 1'b0;
|
| 337 |
|
|
else
|
| 338 |
|
|
br_level_interrupt<= (bw_stopped)&&(~bw_disable_trigger);
|
| 339 |
|
|
|
| 340 |
|
|
endmodule
|