New wishbone-jtag bridge

This commit is contained in:
2026-02-27 15:56:56 +01:00
parent 838204653a
commit 3a9b2acf9e
13 changed files with 1495 additions and 457 deletions

View File

@@ -1,10 +0,0 @@
TRST ABSENT;
ENDIR IDLE;
ENDDR IDLE;
STATE RESET;
STATE IDLE;
SIR 6 TDI (02);
SDR 42 TDI (3A987654321);
STATE IDLE;

124
sim/tb/tb_cdc_strobe_data.v Normal file
View 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
View 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

View File

@@ -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