New wishbone-jtag bridge
This commit is contained in:
124
sim/tb/tb_cdc_strobe_data.v
Normal file
124
sim/tb/tb_cdc_strobe_data.v
Normal file
@@ -0,0 +1,124 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_cdc_strobe_data;
|
||||
|
||||
localparam integer WIDTH = 16;
|
||||
localparam integer NUM_TX = 5;
|
||||
|
||||
// Source domain
|
||||
reg s_clk;
|
||||
reg s_rst;
|
||||
reg s_pulse;
|
||||
reg [WIDTH-1:0] s_data;
|
||||
wire s_busy;
|
||||
wire s_accepted;
|
||||
|
||||
// Destination domain
|
||||
reg d_clk;
|
||||
reg d_rst;
|
||||
wire d_pulse;
|
||||
wire [WIDTH-1:0] d_data;
|
||||
|
||||
reg [WIDTH-1:0] exp_data [0:NUM_TX-1];
|
||||
integer rx_idx;
|
||||
integer tx_idx;
|
||||
|
||||
cdc_strobe_data #(
|
||||
.WIDTH(WIDTH),
|
||||
.STABLE_SAMPLES(2)
|
||||
) dut (
|
||||
.s_clk(s_clk),
|
||||
.s_rst(s_rst),
|
||||
.s_pulse(s_pulse),
|
||||
.s_data(s_data),
|
||||
.s_busy(s_busy),
|
||||
.s_accepted(s_accepted),
|
||||
.d_clk(d_clk),
|
||||
.d_rst(d_rst),
|
||||
.d_pulse(d_pulse),
|
||||
.d_data(d_data)
|
||||
);
|
||||
|
||||
initial s_clk = 1'b0;
|
||||
always #625 s_clk = ~s_clk; // 800 kHz
|
||||
|
||||
initial d_clk = 1'b0;
|
||||
always #33.333 d_clk = ~d_clk; // 15 MHz (asynchronous to s_clk)
|
||||
|
||||
task send_word;
|
||||
input [WIDTH-1:0] value;
|
||||
begin
|
||||
@(posedge s_clk);
|
||||
while (s_busy)
|
||||
@(posedge s_clk);
|
||||
|
||||
s_data <= value;
|
||||
s_pulse <= 1'b1;
|
||||
@(posedge s_clk);
|
||||
|
||||
if (!s_accepted) begin
|
||||
$display("[%0t] ERROR: expected s_accepted for value 0x%0h", $time, value);
|
||||
$fatal(1);
|
||||
end
|
||||
|
||||
s_pulse <= 1'b0;
|
||||
s_data <= {WIDTH{1'b0}};
|
||||
end
|
||||
endtask
|
||||
|
||||
always @(posedge d_clk) begin
|
||||
if (!d_rst && d_pulse) begin
|
||||
if (rx_idx >= NUM_TX) begin
|
||||
$display("[%0t] ERROR: unexpected extra d_pulse with data=0x%0h", $time, d_data);
|
||||
$fatal(1);
|
||||
end
|
||||
|
||||
if (d_data !== exp_data[rx_idx]) begin
|
||||
$display("[%0t] ERROR: rx[%0d] expected 0x%0h, got 0x%0h",
|
||||
$time, rx_idx, exp_data[rx_idx], d_data);
|
||||
$fatal(1);
|
||||
end
|
||||
|
||||
$display("[%0t] INFO: rx[%0d] = 0x%0h", $time, rx_idx, d_data);
|
||||
rx_idx <= rx_idx + 1;
|
||||
end
|
||||
end
|
||||
|
||||
initial begin
|
||||
$dumpfile("out.vcd");
|
||||
$dumpvars(0, tb_cdc_strobe_data);
|
||||
|
||||
s_rst = 1'b1;
|
||||
d_rst = 1'b1;
|
||||
s_pulse = 1'b0;
|
||||
s_data = {WIDTH{1'b0}};
|
||||
rx_idx = 0;
|
||||
tx_idx = 0;
|
||||
|
||||
exp_data[0] = 16'h1234;
|
||||
exp_data[1] = 16'h00A5;
|
||||
exp_data[2] = 16'hBEEF;
|
||||
exp_data[3] = 16'h5AA5;
|
||||
exp_data[4] = 16'hCAFE;
|
||||
|
||||
repeat (4) @(posedge s_clk);
|
||||
s_rst = 1'b0;
|
||||
repeat (3) @(posedge d_clk);
|
||||
d_rst = 1'b0;
|
||||
|
||||
for (tx_idx = 0; tx_idx < NUM_TX; tx_idx = tx_idx + 1)
|
||||
send_word(exp_data[tx_idx]);
|
||||
|
||||
wait (rx_idx == NUM_TX);
|
||||
$display("[%0t] PASS: received %0d/%0d transfers correctly", $time, rx_idx, NUM_TX);
|
||||
#20;
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#100000000;
|
||||
$display("[%0t] ERROR: timeout waiting for transfers", $time);
|
||||
$fatal(1);
|
||||
end
|
||||
|
||||
endmodule
|
||||
334
sim/tb/tb_jtag_wb_bridge.v
Normal file
334
sim/tb/tb_jtag_wb_bridge.v
Normal file
@@ -0,0 +1,334 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_jtag_wb_bridge;
|
||||
|
||||
localparam [7:0] OP_NOP = 8'h00;
|
||||
localparam [7:0] OP_RESET_ON = 8'h10;
|
||||
localparam [7:0] OP_RESET_OFF = 8'h11;
|
||||
localparam [7:0] OP_WRITE8 = 8'h20;
|
||||
localparam [7:0] OP_READ8 = 8'h21;
|
||||
localparam [7:0] OP_PING = 8'h30;
|
||||
|
||||
reg i_clk;
|
||||
reg i_rst;
|
||||
|
||||
wire [31:0] o_wb_adr;
|
||||
wire [31:0] o_wb_dat;
|
||||
wire [3:0] o_wb_sel;
|
||||
wire o_wb_we;
|
||||
wire o_wb_cyc;
|
||||
wire o_wb_stb;
|
||||
reg [31:0] i_wb_rdt;
|
||||
reg i_wb_ack;
|
||||
wire o_cmd_reset;
|
||||
|
||||
reg [7:0] mem [0:1023];
|
||||
reg wb_req_pending;
|
||||
reg [31:0] wb_addr_latched;
|
||||
reg [31:0] wb_dat_latched;
|
||||
reg [3:0] wb_sel_latched;
|
||||
reg wb_we_latched;
|
||||
integer wb_base;
|
||||
|
||||
integer idx;
|
||||
integer req_accept_count;
|
||||
|
||||
reg saw_write_lane3;
|
||||
reg saw_read_lane3;
|
||||
reg saw_cmd_reset_high;
|
||||
reg [47:0] flush_rx;
|
||||
|
||||
jtag_wb_bridge #(
|
||||
.chain(1),
|
||||
.byte_aligned(0)
|
||||
) dut (
|
||||
.i_clk(i_clk),
|
||||
.i_rst(i_rst),
|
||||
.o_wb_adr(o_wb_adr),
|
||||
.o_wb_dat(o_wb_dat),
|
||||
.o_wb_sel(o_wb_sel),
|
||||
.o_wb_we(o_wb_we),
|
||||
.o_wb_cyc(o_wb_cyc),
|
||||
.o_wb_stb(o_wb_stb),
|
||||
.i_wb_rdt(i_wb_rdt),
|
||||
.i_wb_ack(i_wb_ack),
|
||||
.o_cmd_reset(o_cmd_reset)
|
||||
);
|
||||
|
||||
function [47:0] make_cmd;
|
||||
input [7:0] op;
|
||||
input [31:0] addr;
|
||||
input [7:0] data;
|
||||
begin
|
||||
make_cmd = {op, addr, data};
|
||||
end
|
||||
endfunction
|
||||
|
||||
task expect_resp;
|
||||
input [47:0] resp;
|
||||
input [7:0] exp_data;
|
||||
input [7:0] exp_last_op;
|
||||
begin
|
||||
if (resp[23:16] !== exp_data) begin
|
||||
$display("[%0t] ERROR: data exp=0x%0h got=0x%0h", $time, exp_data, resp[23:16]);
|
||||
$fatal(1);
|
||||
end
|
||||
if (resp[7:0] !== exp_last_op) begin
|
||||
$display("[%0t] ERROR: last_op exp=0x%0h got=0x%0h", $time, exp_last_op, resp[7:0]);
|
||||
$fatal(1);
|
||||
end
|
||||
if (resp[37] !== 1'b1) begin
|
||||
$display("[%0t] ERROR: status.resp_valid bit not set in response 0x%012h", $time, resp);
|
||||
$fatal(1);
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task send_cmd;
|
||||
input [47:0] cmd_word;
|
||||
integer tries;
|
||||
integer start_accepts;
|
||||
reg [47:0] dummy_rx;
|
||||
begin
|
||||
start_accepts = req_accept_count;
|
||||
for (tries = 0; tries < 8; tries = tries + 1) begin
|
||||
do_xfer(cmd_word, dummy_rx);
|
||||
if (req_accept_count > start_accepts)
|
||||
tries = 8;
|
||||
end
|
||||
if (req_accept_count <= start_accepts) begin
|
||||
$display("[%0t] ERROR: command 0x%0h was never accepted", $time, cmd_word[47:40]);
|
||||
$fatal(1);
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task expect_eventual_response;
|
||||
input [7:0] exp_data;
|
||||
input [7:0] exp_last_op;
|
||||
integer tries;
|
||||
reg [47:0] rx_word;
|
||||
reg found;
|
||||
begin
|
||||
found = 1'b0;
|
||||
for (tries = 0; tries < 8; tries = tries + 1) begin
|
||||
do_xfer(make_cmd(OP_NOP, 32'h0000_0000, 8'h00), rx_word);
|
||||
if (rx_word[7:0] == exp_last_op) begin
|
||||
expect_resp(rx_word, exp_data, exp_last_op);
|
||||
found = 1'b1;
|
||||
tries = 8;
|
||||
end
|
||||
end
|
||||
if (!found) begin
|
||||
$display("[%0t] ERROR: did not observe response for last_op=0x%0h", $time, exp_last_op);
|
||||
$fatal(1);
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task do_xfer;
|
||||
input [47:0] tx_word;
|
||||
output [47:0] rx_word;
|
||||
begin
|
||||
dut.u_jtag.transceive48(tx_word, rx_word);
|
||||
repeat (400) @(posedge i_clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
initial i_clk = 1'b0;
|
||||
always #10 i_clk = ~i_clk; // 50 MHz
|
||||
|
||||
always @(posedge i_clk) begin
|
||||
if (i_rst) begin
|
||||
i_wb_ack <= 1'b0;
|
||||
i_wb_rdt <= 32'h0;
|
||||
wb_req_pending <= 1'b0;
|
||||
wb_addr_latched <= 32'h0;
|
||||
wb_dat_latched <= 32'h0;
|
||||
wb_sel_latched <= 4'h0;
|
||||
wb_we_latched <= 1'b0;
|
||||
end else begin
|
||||
if (o_cmd_reset)
|
||||
saw_cmd_reset_high <= 1'b1;
|
||||
|
||||
i_wb_ack <= 1'b0;
|
||||
|
||||
if (wb_req_pending) begin
|
||||
wb_base = {wb_addr_latched[31:2], 2'b00};
|
||||
|
||||
if (wb_we_latched) begin
|
||||
if (wb_sel_latched[0]) mem[wb_base + 0] <= wb_dat_latched[7:0];
|
||||
if (wb_sel_latched[1]) mem[wb_base + 1] <= wb_dat_latched[15:8];
|
||||
if (wb_sel_latched[2]) mem[wb_base + 2] <= wb_dat_latched[23:16];
|
||||
if (wb_sel_latched[3]) mem[wb_base + 3] <= wb_dat_latched[31:24];
|
||||
end
|
||||
|
||||
i_wb_rdt <= {mem[wb_base + 3], mem[wb_base + 2], mem[wb_base + 1], mem[wb_base + 0]};
|
||||
i_wb_ack <= 1'b1;
|
||||
wb_req_pending <= 1'b0;
|
||||
end else if (o_wb_cyc && o_wb_stb) begin
|
||||
wb_addr_latched <= o_wb_adr;
|
||||
wb_dat_latched <= o_wb_dat;
|
||||
wb_sel_latched <= o_wb_sel;
|
||||
wb_we_latched <= o_wb_we;
|
||||
wb_req_pending <= 1'b1;
|
||||
|
||||
if (o_wb_we && (o_wb_adr == 32'h0000_0013) && (o_wb_sel == 4'b1000) && (o_wb_dat == 32'h5A00_0000))
|
||||
saw_write_lane3 <= 1'b1;
|
||||
if (!o_wb_we && (o_wb_adr == 32'h0000_0013) && (o_wb_sel == 4'b1000))
|
||||
saw_read_lane3 <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge dut.jtag_tck) begin
|
||||
if (i_rst)
|
||||
req_accept_count <= 0;
|
||||
else if (dut.a_req_accepted)
|
||||
req_accept_count <= req_accept_count + 1;
|
||||
end
|
||||
|
||||
initial begin
|
||||
$dumpfile("out.vcd");
|
||||
$dumpvars(0, tb_jtag_wb_bridge);
|
||||
|
||||
i_rst = 1'b1;
|
||||
i_wb_rdt = 32'h0;
|
||||
i_wb_ack = 1'b0;
|
||||
wb_req_pending = 1'b0;
|
||||
wb_addr_latched = 32'h0;
|
||||
wb_dat_latched = 32'h0;
|
||||
wb_sel_latched = 4'h0;
|
||||
wb_we_latched = 1'b0;
|
||||
saw_write_lane3 = 1'b0;
|
||||
saw_read_lane3 = 1'b0;
|
||||
saw_cmd_reset_high = 1'b0;
|
||||
req_accept_count = 0;
|
||||
|
||||
for (idx = 0; idx < 1024; idx = idx + 1)
|
||||
mem[idx] = idx[7:0];
|
||||
|
||||
repeat (10) @(posedge i_clk);
|
||||
i_rst = 1'b0;
|
||||
|
||||
repeat (10) @(posedge i_clk);
|
||||
|
||||
send_cmd(make_cmd(OP_PING, 32'h0000_0000, 8'h00));
|
||||
expect_eventual_response(8'hA5, OP_PING);
|
||||
|
||||
send_cmd(make_cmd(OP_WRITE8, 32'h0000_0013, 8'h5A));
|
||||
send_cmd(make_cmd(OP_READ8, 32'h0000_0013, 8'h00));
|
||||
send_cmd(make_cmd(OP_RESET_ON, 32'h0000_0000, 8'h00));
|
||||
send_cmd(make_cmd(OP_RESET_OFF, 32'h0000_0000, 8'h00));
|
||||
|
||||
repeat (6)
|
||||
do_xfer(make_cmd(OP_NOP, 32'h0000_0000, 8'h00), flush_rx);
|
||||
|
||||
if (!saw_write_lane3) begin
|
||||
$display("[%0t] ERROR: expected WRITE8 lane-3 WB access not observed", $time);
|
||||
$fatal(1);
|
||||
end
|
||||
if (!saw_read_lane3) begin
|
||||
$display("[%0t] ERROR: expected READ8 lane-3 WB access not observed", $time);
|
||||
$fatal(1);
|
||||
end
|
||||
if (!saw_cmd_reset_high) begin
|
||||
$display("[%0t] ERROR: expected o_cmd_reset to go high at least once", $time);
|
||||
$fatal(1);
|
||||
end
|
||||
|
||||
repeat (50) @(posedge i_clk);
|
||||
if (o_cmd_reset !== 1'b0) begin
|
||||
$display("[%0t] ERROR: expected o_cmd_reset low after RESET_OFF, got %0b", $time, o_cmd_reset);
|
||||
$fatal(1);
|
||||
end
|
||||
|
||||
$display("[%0t] PASS: jtag_wb_bridge basic command/response path verified", $time);
|
||||
#100;
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#5_000_000;
|
||||
$display("[%0t] ERROR: timeout", $time);
|
||||
$fatal(1);
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
module jtag_if #(
|
||||
parameter integer chain = 1
|
||||
)(
|
||||
input wire i_tdo,
|
||||
output reg o_tck,
|
||||
output reg o_tdi,
|
||||
output reg o_drck,
|
||||
output reg o_capture,
|
||||
output reg o_shift,
|
||||
output reg o_update,
|
||||
output reg o_runtest,
|
||||
output reg o_reset,
|
||||
output reg o_sel
|
||||
);
|
||||
integer k;
|
||||
|
||||
// Quiet unused parameter warning
|
||||
wire _unused_chain;
|
||||
assign _unused_chain = chain[0];
|
||||
|
||||
task pulse_tck;
|
||||
begin
|
||||
#40 o_tck = 1'b1;
|
||||
#40 o_tck = 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
task transceive48;
|
||||
input [47:0] tx_word;
|
||||
output [47:0] rx_word;
|
||||
begin
|
||||
rx_word = 48'h0;
|
||||
|
||||
// Let CDC response path advance in the TCK domain before CAPTURE.
|
||||
for (k = 0; k < 32; k = k + 1)
|
||||
pulse_tck;
|
||||
|
||||
// CAPTURE-DR
|
||||
o_capture = 1'b1;
|
||||
pulse_tck;
|
||||
o_capture = 1'b0;
|
||||
|
||||
// SHIFT-DR (read previous response while shifting next command in)
|
||||
o_shift = 1'b1;
|
||||
for (k = 0; k < 48; k = k + 1) begin
|
||||
o_tdi = tx_word[k];
|
||||
rx_word[k] = i_tdo;
|
||||
o_drck = 1'b1;
|
||||
pulse_tck;
|
||||
o_drck = 1'b0;
|
||||
end
|
||||
o_shift = 1'b0;
|
||||
|
||||
// UPDATE-DR (request strobe sampled by TCK)
|
||||
o_update = 1'b1;
|
||||
pulse_tck;
|
||||
o_update = 1'b0;
|
||||
|
||||
// Provide a few extra clocks after UPDATE.
|
||||
for (k = 0; k < 8; k = k + 1)
|
||||
pulse_tck;
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
o_tck = 1'b0;
|
||||
o_tdi = 1'b0;
|
||||
o_drck = 1'b0;
|
||||
o_capture = 1'b0;
|
||||
o_shift = 1'b0;
|
||||
o_update = 1'b0;
|
||||
o_runtest = 1'b0;
|
||||
o_reset = 1'b0;
|
||||
o_sel = 1'b1;
|
||||
end
|
||||
endmodule
|
||||
106
sim/tb/tb_svf.v
106
sim/tb/tb_svf.v
@@ -1,106 +0,0 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_svf();
|
||||
reg clk;
|
||||
reg resetn;
|
||||
initial clk <= 1'b0;
|
||||
initial resetn <= 1'b0;
|
||||
always #33.33 clk <= !clk;
|
||||
initial #40 resetn <= 1'b1;
|
||||
|
||||
wire [7:0] led_out;
|
||||
|
||||
jtag_byte_sink #(
|
||||
.SVF_FILE("sim/other/test.svf"),
|
||||
.TCK_HALF_PERIOD_NS(500)
|
||||
) dut (
|
||||
.i_clk(clk),
|
||||
.i_rst(!resetn),
|
||||
.o_led(led_out)
|
||||
);
|
||||
|
||||
initial begin
|
||||
$dumpfile("out.vcd");
|
||||
$dumpvars;
|
||||
#200_000;
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module jtag_byte_sink #(
|
||||
parameter [8*256-1:0] SVF_FILE = "",
|
||||
parameter integer TCK_HALF_PERIOD_NS = 50
|
||||
)(
|
||||
input wire i_clk,
|
||||
input wire i_rst,
|
||||
output reg [7:0] o_led
|
||||
);
|
||||
|
||||
initial o_led <= 0;
|
||||
|
||||
// JTAG interface wires
|
||||
wire jtag_tck;
|
||||
wire jtag_tdi;
|
||||
wire jtag_drck;
|
||||
wire jtag_capture;
|
||||
wire jtag_shift;
|
||||
wire jtag_update;
|
||||
wire jtag_runtest;
|
||||
wire jtag_reset;
|
||||
wire jtag_sel;
|
||||
|
||||
reg [41:0] jtag_q;
|
||||
reg [41:0] jtag_data;
|
||||
wire jtag_async_reset;
|
||||
|
||||
jtag_if #(
|
||||
.chain(1),
|
||||
.SVF_FILE(SVF_FILE),
|
||||
.TCK_HALF_PERIOD_NS(TCK_HALF_PERIOD_NS),
|
||||
.USER_IR_OPCODE(32'h0000_0002)
|
||||
) jtag (
|
||||
.i_tdo(jtag_q[0]),
|
||||
.o_tck(jtag_tck),
|
||||
.o_tdi(jtag_tdi),
|
||||
.o_drck(jtag_drck),
|
||||
.o_capture(jtag_capture),
|
||||
.o_shift(jtag_shift),
|
||||
.o_update(jtag_update),
|
||||
.o_runtest(jtag_runtest),
|
||||
.o_reset(jtag_reset),
|
||||
.o_sel(jtag_sel)
|
||||
);
|
||||
|
||||
|
||||
assign jtag_async_reset = jtag_reset || i_rst;
|
||||
|
||||
always @(posedge jtag_drck or posedge jtag_async_reset) begin
|
||||
if (jtag_async_reset) begin
|
||||
jtag_q <= 0;
|
||||
end else if (jtag_sel && jtag_capture) begin
|
||||
jtag_q <= jtag_data;
|
||||
end else if (jtag_sel && jtag_shift) begin
|
||||
jtag_q <= {jtag_tdi, jtag_q[41:1]};
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge jtag_update or posedge jtag_async_reset) begin
|
||||
if (jtag_async_reset) begin
|
||||
jtag_data <= 0;
|
||||
end else if (jtag_sel) begin
|
||||
jtag_data <= jtag_q;
|
||||
end
|
||||
end
|
||||
|
||||
wire [41:0] j_data;
|
||||
wire j_data_update;
|
||||
cdc_strobed #(42) j_data_cdc (
|
||||
.i_clk_a(i_clk),
|
||||
.i_clk_b(i_clk),
|
||||
.i_data(jtag_data),
|
||||
.i_strobe(jtag_update),
|
||||
.o_data(j_data),
|
||||
.o_strobe(j_data_update)
|
||||
);
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user