598 lines
16 KiB
Verilog
598 lines
16 KiB
Verilog
module logic_analyzer (
|
|
input wire clk,
|
|
|
|
// probes
|
|
input wire larry,
|
|
input wire curly,
|
|
input wire moe,
|
|
input wire [3:0] shemp,
|
|
|
|
// input port
|
|
input wire [15:0] addr_i,
|
|
input wire [15:0] wdata_i,
|
|
input wire [15:0] rdata_i,
|
|
input wire rw_i,
|
|
input wire valid_i,
|
|
|
|
// output port
|
|
output reg [15:0] addr_o,
|
|
output reg [15:0] wdata_o,
|
|
output reg [15:0] rdata_o,
|
|
output reg rw_o,
|
|
output reg valid_o
|
|
);
|
|
localparam SAMPLE_DEPTH = 128;
|
|
localparam ADDR_WIDTH = $clog2(SAMPLE_DEPTH);
|
|
|
|
reg [3:0] state;
|
|
reg signed [15:0] trigger_loc;
|
|
reg signed [15:0] current_loc;
|
|
reg request_start;
|
|
reg request_stop;
|
|
reg [ADDR_WIDTH-1:0] read_pointer;
|
|
|
|
reg trig;
|
|
|
|
reg [ADDR_WIDTH-1:0] bram_addr;
|
|
reg bram_we;
|
|
|
|
localparam TOTAL_PROBE_WIDTH = 7;
|
|
reg [TOTAL_PROBE_WIDTH-1:0] probes_concat;
|
|
assign probes_concat = {larry, curly, moe, shemp};
|
|
|
|
logic_analyzer_controller #(.SAMPLE_DEPTH(SAMPLE_DEPTH)) la_controller (
|
|
.clk(clk),
|
|
|
|
// from register file
|
|
.state(state),
|
|
.trigger_loc(trigger_loc),
|
|
.current_loc(current_loc),
|
|
.request_start(request_start),
|
|
.request_stop(request_stop),
|
|
.read_pointer(read_pointer),
|
|
|
|
// from trigger block
|
|
.trig(trig),
|
|
|
|
// from block memory user port
|
|
.bram_addr(bram_addr),
|
|
.bram_we(bram_we)
|
|
);
|
|
|
|
logic_analyzer_fsm_registers #(
|
|
.BASE_ADDR(0),
|
|
.SAMPLE_DEPTH(SAMPLE_DEPTH)
|
|
) fsm_registers (
|
|
.clk(clk),
|
|
|
|
.addr_i(addr_i),
|
|
.wdata_i(wdata_i),
|
|
.rdata_i(rdata_i),
|
|
.rw_i(rw_i),
|
|
.valid_i(valid_i),
|
|
|
|
.addr_o(fsm_reg_trig_blk_addr),
|
|
.wdata_o(fsm_reg_trig_blk_wdata),
|
|
.rdata_o(fsm_reg_trig_blk_rdata),
|
|
.rw_o(fsm_reg_trig_blk_rw),
|
|
.valid_o(fsm_reg_trig_blk_valid),
|
|
|
|
.state(state),
|
|
.trigger_loc(trigger_loc),
|
|
.current_loc(current_loc),
|
|
.request_start(request_start),
|
|
.request_stop(request_stop),
|
|
.read_pointer(read_pointer));
|
|
|
|
reg [15:0] fsm_reg_trig_blk_addr;
|
|
reg [15:0] fsm_reg_trig_blk_wdata;
|
|
reg [15:0] fsm_reg_trig_blk_rdata;
|
|
reg fsm_reg_trig_blk_rw;
|
|
reg fsm_reg_trig_blk_valid;
|
|
|
|
// trigger block
|
|
trigger_block #(.BASE_ADDR(6)) trig_blk (
|
|
.clk(clk),
|
|
|
|
.larry(larry),
|
|
.curly(curly),
|
|
.moe(moe),
|
|
.shemp(shemp),
|
|
|
|
.trig(trig),
|
|
|
|
.addr_i(fsm_reg_trig_blk_addr),
|
|
.wdata_i(fsm_reg_trig_blk_wdata),
|
|
.rdata_i(fsm_reg_trig_blk_rdata),
|
|
.rw_i(fsm_reg_trig_blk_rw),
|
|
.valid_i(fsm_reg_trig_blk_valid),
|
|
|
|
.addr_o(trig_blk_block_mem_addr),
|
|
.wdata_o(trig_blk_block_mem_wdata),
|
|
.rdata_o(trig_blk_block_mem_rdata),
|
|
.rw_o(trig_blk_block_mem_rw),
|
|
.valid_o(trig_blk_block_mem_valid));
|
|
|
|
reg [15:0] trig_blk_block_mem_addr;
|
|
reg [15:0] trig_blk_block_mem_wdata;
|
|
reg [15:0] trig_blk_block_mem_rdata;
|
|
reg trig_blk_block_mem_rw;
|
|
reg trig_blk_block_mem_valid;
|
|
|
|
// sample memory
|
|
block_memory #(
|
|
.BASE_ADDR(14),
|
|
.WIDTH(TOTAL_PROBE_WIDTH),
|
|
.DEPTH(SAMPLE_DEPTH)
|
|
) block_mem (
|
|
.clk(clk),
|
|
|
|
// input port
|
|
.addr_i(trig_blk_block_mem_addr),
|
|
.wdata_i(trig_blk_block_mem_wdata),
|
|
.rdata_i(trig_blk_block_mem_rdata),
|
|
.rw_i(trig_blk_block_mem_rw),
|
|
.valid_i(trig_blk_block_mem_valid),
|
|
|
|
// output port
|
|
.addr_o(addr_o),
|
|
.wdata_o(wdata_o),
|
|
.rdata_o(rdata_o),
|
|
.rw_o(rw_o),
|
|
.valid_o(valid_o),
|
|
|
|
// BRAM itself
|
|
.user_clk(clk),
|
|
.user_addr(bram_addr),
|
|
.user_din(probes_concat),
|
|
.user_dout(),
|
|
.user_we(bram_we));
|
|
endmodule
|
|
module logic_analyzer_controller (
|
|
input wire clk,
|
|
|
|
// from register file
|
|
output reg [3:0] state,
|
|
input wire signed [15:0] trigger_loc,
|
|
output reg signed [15:0] current_loc,
|
|
input wire request_start,
|
|
input wire request_stop,
|
|
output reg [ADDR_WIDTH-1:0] read_pointer,
|
|
|
|
// from trigger block
|
|
input wire trig,
|
|
|
|
// block memory user port
|
|
output [ADDR_WIDTH-1:0] bram_addr,
|
|
output bram_we
|
|
);
|
|
|
|
assign bram_addr = write_pointer;
|
|
assign bram_we = acquire;
|
|
|
|
parameter SAMPLE_DEPTH= 0;
|
|
localparam ADDR_WIDTH = $clog2(SAMPLE_DEPTH);
|
|
|
|
/* ----- FSM ----- */
|
|
localparam IDLE = 0;
|
|
localparam MOVE_TO_POSITION = 1;
|
|
localparam IN_POSITION = 2;
|
|
localparam CAPTURING = 3;
|
|
localparam CAPTURED = 4;
|
|
|
|
initial state = IDLE;
|
|
initial current_loc = 0;
|
|
|
|
// rising edge detection for start/stop requests
|
|
reg prev_request_start;
|
|
always @(posedge clk) prev_request_start <= request_start;
|
|
|
|
reg prev_request_stop;
|
|
always @(posedge clk) prev_request_stop <= request_stop;
|
|
|
|
always @(posedge clk) begin
|
|
// don't do anything to the FIFO unless told to
|
|
acquire <= 0;
|
|
pop <= 0;
|
|
|
|
if(state == IDLE) begin
|
|
clear <= 1;
|
|
|
|
if(request_start && ~prev_request_start) begin
|
|
// TODO: figure out what determines whether or not we
|
|
// go into MOVE_TO_POSITION or IN_POSITION. that's for
|
|
// the morning
|
|
state <= MOVE_TO_POSITION;
|
|
clear <= 0;
|
|
end
|
|
end
|
|
|
|
else if(state == MOVE_TO_POSITION) begin
|
|
acquire <= 1;
|
|
current_loc <= current_loc + 1;
|
|
|
|
if(current_loc == trigger_loc) state <= IN_POSITION;
|
|
end
|
|
|
|
else if(state == IN_POSITION) begin
|
|
acquire <= 1;
|
|
pop <= 1;
|
|
|
|
if(trig) pop <= 0;
|
|
if(trig) state <= CAPTURING;
|
|
end
|
|
|
|
else if(state == CAPTURING) begin
|
|
acquire <= 1;
|
|
|
|
if(size == SAMPLE_DEPTH) begin
|
|
state <= CAPTURED;
|
|
acquire <= 0;
|
|
end
|
|
end
|
|
|
|
else if(state == CAPTURED) begin
|
|
// actually nothing to do here doooodeeedoooo
|
|
end
|
|
|
|
else if(request_stop && ~prev_request_stop) state <= IDLE;
|
|
|
|
else state <= IDLE;
|
|
end
|
|
|
|
|
|
/* ----- FIFO ----- */
|
|
reg acquire;
|
|
reg pop;
|
|
reg [ADDR_WIDTH:0] size;
|
|
reg clear;
|
|
|
|
reg [ADDR_WIDTH:0] write_pointer = 0;
|
|
initial read_pointer = 0;
|
|
initial write_pointer = 0;
|
|
|
|
assign size = write_pointer - read_pointer;
|
|
|
|
always @(posedge clk) begin
|
|
if (clear) read_pointer <= write_pointer;
|
|
if (acquire && size < SAMPLE_DEPTH) write_pointer <= write_pointer + 1'd1;
|
|
if (pop && size > 0) read_pointer <= read_pointer + 1'd1;
|
|
end
|
|
endmodule
|
|
module block_memory (
|
|
input wire clk,
|
|
|
|
// input port
|
|
input wire [15:0] addr_i,
|
|
input wire [15:0] wdata_i,
|
|
input wire [15:0] rdata_i,
|
|
input wire rw_i,
|
|
input wire valid_i,
|
|
|
|
// output port
|
|
output reg [15:0] addr_o,
|
|
output reg [15:0] wdata_o,
|
|
output reg [15:0] rdata_o,
|
|
output reg rw_o,
|
|
output reg valid_o,
|
|
|
|
// BRAM itself
|
|
input wire user_clk,
|
|
input wire [ADDR_WIDTH-1:0] user_addr,
|
|
input wire [WIDTH-1:0] user_din,
|
|
output reg [WIDTH-1:0] user_dout,
|
|
input wire user_we);
|
|
|
|
parameter BASE_ADDR = 0;
|
|
parameter WIDTH = 0;
|
|
parameter DEPTH = 0;
|
|
localparam ADDR_WIDTH = $clog2(DEPTH);
|
|
|
|
// ugly typecasting, but just computes ceil(WIDTH / 16)
|
|
localparam N_BRAMS = int'($ceil(real'(WIDTH) / 16.0));
|
|
localparam MAX_ADDR = BASE_ADDR + (DEPTH * N_BRAMS);
|
|
|
|
// Port A of BRAMs
|
|
reg [N_BRAMS-1:0][ADDR_WIDTH-1:0] addra = 0;
|
|
reg [N_BRAMS-1:0][15:0] dina = 0;
|
|
reg [N_BRAMS-1:0][15:0] douta;
|
|
reg [N_BRAMS-1:0] wea = 0;
|
|
|
|
// Port B of BRAMs
|
|
reg [N_BRAMS-1:0][15:0] dinb;
|
|
reg [N_BRAMS-1:0][15:0] doutb;
|
|
assign dinb = user_din;
|
|
|
|
// kind of a hack to part select from a 2d array that's been flattened to 1d
|
|
reg [(N_BRAMS*16)-1:0] doutb_flattened;
|
|
assign doutb_flattened = doutb;
|
|
assign user_dout = doutb_flattened[WIDTH-1:0];
|
|
|
|
// Pipelining
|
|
reg [2:0][15:0] addr_pipe = 0;
|
|
reg [2:0][15:0] wdata_pipe = 0;
|
|
reg [2:0][15:0] rdata_pipe = 0;
|
|
reg [2:0] valid_pipe = 0;
|
|
reg [2:0] rw_pipe = 0;
|
|
|
|
always @(posedge clk) begin
|
|
addr_pipe[0] <= addr_i;
|
|
wdata_pipe[0] <= wdata_i;
|
|
rdata_pipe[0] <= rdata_i;
|
|
valid_pipe[0] <= valid_i;
|
|
rw_pipe[0] <= rw_i;
|
|
|
|
addr_o <= addr_pipe[2];
|
|
wdata_o <= wdata_pipe[2];
|
|
rdata_o <= rdata_pipe[2];
|
|
valid_o <= valid_pipe[2];
|
|
rw_o <= rw_pipe[2];
|
|
|
|
for(int i=1; i<3; i=i+1) begin
|
|
addr_pipe[i] <= addr_pipe[i-1];
|
|
wdata_pipe[i] <= wdata_pipe[i-1];
|
|
rdata_pipe[i] <= rdata_pipe[i-1];
|
|
valid_pipe[i] <= valid_pipe[i-1];
|
|
rw_pipe[i] <= rw_pipe[i-1];
|
|
end
|
|
|
|
// throw BRAM operations into the front of the pipeline
|
|
wea <= 0;
|
|
if( (valid_i) && (addr_i >= BASE_ADDR) && (addr_i <= MAX_ADDR)) begin
|
|
wea[addr_i % N_BRAMS] <= rw_i;
|
|
addra[addr_i % N_BRAMS] <= (addr_i - BASE_ADDR) / N_BRAMS;
|
|
dina[addr_i % N_BRAMS] <= wdata_i;
|
|
end
|
|
|
|
// pull BRAM reads from the back of the pipeline
|
|
if( (valid_pipe[2]) && (addr_pipe[2] >= BASE_ADDR) && (addr_pipe[2] <= MAX_ADDR)) begin
|
|
rdata_o <= douta[addr_pipe[2] % N_BRAMS];
|
|
end
|
|
end
|
|
|
|
// generate the BRAMs
|
|
genvar i;
|
|
generate
|
|
for(i=0; i<N_BRAMS; i=i+1) begin
|
|
dual_port_bram #(
|
|
.RAM_WIDTH(16),
|
|
.RAM_DEPTH(DEPTH)
|
|
) bram_full_width_i (
|
|
|
|
// port A is controlled by the bus
|
|
.clka(clk),
|
|
.addra(addra[i]),
|
|
.dina(dina[i]),
|
|
.douta(douta[i]),
|
|
.wea(wea[i]),
|
|
|
|
// port B is exposed to the user
|
|
.clkb(user_clk),
|
|
.addrb(user_addr),
|
|
.dinb(dinb[i]),
|
|
.doutb(doutb[i]),
|
|
.web(user_we));
|
|
end
|
|
endgenerate
|
|
endmodule
|
|
// Xilinx True Dual Port RAM, Read First, Dual Clock
|
|
// This code implements a parameterizable true dual port memory (both ports can read and write).
|
|
// The behavior of this RAM is when data is written, the prior memory contents at the write
|
|
// address are presented on the output port. If the output data is
|
|
// not needed during writes or the last read value is desired to be retained,
|
|
// it is suggested to use a no change RAM as it is more power efficient.
|
|
// If a reset or enable is not necessary, it may be tied off or removed from the code.
|
|
|
|
// Modified from the xilinx_true_dual_port_read_first_2_clock_ram verilog language template.
|
|
|
|
module dual_port_bram #(
|
|
parameter RAM_WIDTH = 0,
|
|
parameter RAM_DEPTH = 0
|
|
) (
|
|
input wire [$clog2(RAM_DEPTH-1)-1:0] addra,
|
|
input wire [$clog2(RAM_DEPTH-1)-1:0] addrb,
|
|
input wire [RAM_WIDTH-1:0] dina,
|
|
input wire [RAM_WIDTH-1:0] dinb,
|
|
input wire clka,
|
|
input wire clkb,
|
|
input wire wea,
|
|
input wire web,
|
|
output wire [RAM_WIDTH-1:0] douta,
|
|
output wire [RAM_WIDTH-1:0] doutb
|
|
);
|
|
|
|
// The following code either initializes the memory values to a specified file or to all zeros to match hardware
|
|
generate
|
|
integer i;
|
|
initial begin
|
|
for (i = 0; i < RAM_DEPTH; i = i + 1)
|
|
BRAM[i] = {RAM_WIDTH{1'b0}};
|
|
end
|
|
endgenerate
|
|
|
|
reg [RAM_WIDTH-1:0] BRAM [RAM_DEPTH-1:0];
|
|
reg [RAM_WIDTH-1:0] ram_data_a = {RAM_WIDTH{1'b0}};
|
|
reg [RAM_WIDTH-1:0] ram_data_b = {RAM_WIDTH{1'b0}};
|
|
|
|
always @(posedge clka) begin
|
|
if (wea) BRAM[addra] <= dina;
|
|
ram_data_a <= BRAM[addra];
|
|
end
|
|
|
|
always @(posedge clkb) begin
|
|
if (web) BRAM[addrb] <= dinb;
|
|
ram_data_b <= BRAM[addrb];
|
|
end
|
|
|
|
// Add a 2 clock cycle read latency to improve clock-to-out timing
|
|
reg [RAM_WIDTH-1:0] douta_reg = {RAM_WIDTH{1'b0}};
|
|
reg [RAM_WIDTH-1:0] doutb_reg = {RAM_WIDTH{1'b0}};
|
|
|
|
always @(posedge clka) douta_reg <= ram_data_a;
|
|
always @(posedge clkb) doutb_reg <= ram_data_b;
|
|
|
|
assign douta = douta_reg;
|
|
assign doutb = doutb_reg;
|
|
endmodule
|
|
module trigger_block (
|
|
input wire clk,
|
|
|
|
// probes
|
|
input wire larry,
|
|
input wire curly,
|
|
input wire moe,
|
|
input wire [3:0] shemp,
|
|
|
|
// trigger
|
|
output reg trig,
|
|
|
|
// input port
|
|
input wire [15:0] addr_i,
|
|
input wire [15:0] wdata_i,
|
|
input wire [15:0] rdata_i,
|
|
input wire rw_i,
|
|
input wire valid_i,
|
|
|
|
// output port
|
|
output reg [15:0] addr_o,
|
|
output reg [15:0] wdata_o,
|
|
output reg [15:0] rdata_o,
|
|
output reg rw_o,
|
|
output reg valid_o);
|
|
|
|
parameter BASE_ADDR = 0;
|
|
localparam MAX_ADDR = 7;
|
|
|
|
// trigger configuration registers
|
|
// - each probe gets an operation and a compare register
|
|
// - at the end we OR them all together. along with any custom probes the user specs
|
|
|
|
reg [3:0] larry_op = 0;
|
|
reg larry_arg = 0;
|
|
reg larry_trig;
|
|
|
|
trigger #(.INPUT_WIDTH(1)) larry_trigger (
|
|
.clk(clk),
|
|
|
|
.probe(larry),
|
|
.op(larry_op),
|
|
.arg(larry_arg),
|
|
.trig(larry_trig));
|
|
reg [3:0] curly_op = 0;
|
|
reg curly_arg = 0;
|
|
reg curly_trig;
|
|
|
|
trigger #(.INPUT_WIDTH(1)) curly_trigger (
|
|
.clk(clk),
|
|
|
|
.probe(curly),
|
|
.op(curly_op),
|
|
.arg(curly_arg),
|
|
.trig(curly_trig));
|
|
reg [3:0] moe_op = 0;
|
|
reg moe_arg = 0;
|
|
reg moe_trig;
|
|
|
|
trigger #(.INPUT_WIDTH(1)) moe_trigger (
|
|
.clk(clk),
|
|
|
|
.probe(moe),
|
|
.op(moe_op),
|
|
.arg(moe_arg),
|
|
.trig(moe_trig));
|
|
reg [3:0] shemp_op = 0;
|
|
reg [3:0] shemp_arg = 0;
|
|
reg shemp_trig;
|
|
|
|
trigger #(.INPUT_WIDTH(4)) shemp_trigger (
|
|
.clk(clk),
|
|
|
|
.probe(shemp),
|
|
.op(shemp_op),
|
|
.arg(shemp_arg),
|
|
.trig(shemp_trig));
|
|
|
|
assign trig = larry_trig || curly_trig || moe_trig || shemp_trig;
|
|
|
|
// perform register operations
|
|
always @(posedge clk) begin
|
|
addr_o <= addr_i;
|
|
wdata_o <= wdata_i;
|
|
rdata_o <= rdata_i;
|
|
rw_o <= rw_i;
|
|
valid_o <= valid_i;
|
|
rdata_o <= rdata_i;
|
|
|
|
if( (addr_i >= BASE_ADDR) && (addr_i <= BASE_ADDR + MAX_ADDR) ) begin
|
|
|
|
// reads
|
|
if(valid_i && !rw_i) begin
|
|
case (addr_i)
|
|
BASE_ADDR + 0: rdata_o <= larry_op;
|
|
BASE_ADDR + 1: rdata_o <= larry_arg;
|
|
BASE_ADDR + 2: rdata_o <= curly_op;
|
|
BASE_ADDR + 3: rdata_o <= curly_arg;
|
|
BASE_ADDR + 4: rdata_o <= moe_op;
|
|
BASE_ADDR + 5: rdata_o <= moe_arg;
|
|
BASE_ADDR + 6: rdata_o <= shemp_op;
|
|
BASE_ADDR + 7: rdata_o <= shemp_arg;
|
|
endcase
|
|
end
|
|
|
|
// writes
|
|
else if(valid_i && rw_i) begin
|
|
case (addr_i)
|
|
BASE_ADDR + 0: larry_op <= wdata_i;
|
|
BASE_ADDR + 1: larry_arg <= wdata_i;
|
|
BASE_ADDR + 2: curly_op <= wdata_i;
|
|
BASE_ADDR + 3: curly_arg <= wdata_i;
|
|
BASE_ADDR + 4: moe_op <= wdata_i;
|
|
BASE_ADDR + 5: moe_arg <= wdata_i;
|
|
BASE_ADDR + 6: shemp_op <= wdata_i;
|
|
BASE_ADDR + 7: shemp_arg <= wdata_i;
|
|
endcase
|
|
end
|
|
end
|
|
end
|
|
endmodule
|
|
module trigger (
|
|
input wire clk,
|
|
|
|
input wire [INPUT_WIDTH-1:0] probe,
|
|
input wire [3:0] op,
|
|
input wire [INPUT_WIDTH-1:0] arg,
|
|
|
|
output reg trig);
|
|
|
|
parameter INPUT_WIDTH = 0;
|
|
|
|
localparam DISABLE = 0;
|
|
localparam RISING = 1;
|
|
localparam FALLING = 2;
|
|
localparam CHANGING = 3;
|
|
localparam GT = 4;
|
|
localparam LT = 5;
|
|
localparam GEQ = 6;
|
|
localparam LEQ = 7;
|
|
localparam EQ = 8;
|
|
localparam NEQ = 9;
|
|
|
|
reg [INPUT_WIDTH-1:0] probe_prev = 0;
|
|
always @(posedge clk) probe_prev <= probe;
|
|
|
|
always @(*) begin
|
|
case (op)
|
|
RISING : trig = (probe > probe_prev);
|
|
FALLING : trig = (probe < probe_prev);
|
|
CHANGING : trig = (probe != probe_prev);
|
|
GT: trig = (probe > arg);
|
|
LT: trig = (probe < arg);
|
|
GEQ: trig = (probe >= arg);
|
|
LEQ: trig = (probe <= arg);
|
|
EQ: trig = (probe == arg);
|
|
NEQ: trig = (probe != arg);
|
|
default: trig = 0;
|
|
endcase
|
|
end
|
|
endmodule
|