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

70
rtl/core/cdc_req_resp.v Normal file
View File

@@ -0,0 +1,70 @@
`timescale 1 ns/1 ps
// =============================================================================
// cdc_req_resp
// Bidirectional channel made from two cdc_strobe_data mailboxes.
// =============================================================================
module cdc_req_resp #(
parameter integer REQ_W = 32,
parameter integer RESP_W = 32,
parameter integer STABLE_SAMPLES = 2
)(
// Side A (e.g., JTAG/TCK)
input wire a_clk,
input wire a_rst,
input wire a_req_pulse,
input wire [REQ_W-1:0] a_req_data,
output wire a_req_busy,
output wire a_req_accepted,
output wire a_resp_pulse,
output wire [RESP_W-1:0] a_resp_data,
// Side B (e.g., system/i_clk)
input wire b_clk,
input wire b_rst,
output wire b_req_pulse,
output wire [REQ_W-1:0] b_req_data,
input wire b_resp_pulse,
input wire [RESP_W-1:0] b_resp_data,
output wire b_resp_busy,
output wire b_resp_accepted
);
cdc_strobe_data #(
.WIDTH(REQ_W),
.STABLE_SAMPLES(STABLE_SAMPLES)
) u_req (
.s_clk(a_clk),
.s_rst(a_rst),
.s_pulse(a_req_pulse),
.s_data(a_req_data),
.s_busy(a_req_busy),
.s_accepted(a_req_accepted),
.d_clk(b_clk),
.d_rst(b_rst),
.d_pulse(b_req_pulse),
.d_data(b_req_data)
);
cdc_strobe_data #(
.WIDTH(RESP_W),
.STABLE_SAMPLES(STABLE_SAMPLES)
) u_resp (
.s_clk(b_clk),
.s_rst(b_rst),
.s_pulse(b_resp_pulse),
.s_data(b_resp_data),
.s_busy(b_resp_busy),
.s_accepted(b_resp_accepted),
.d_clk(a_clk),
.d_rst(a_rst),
.d_pulse(a_resp_pulse),
.d_data(a_resp_data)
);
endmodule

130
rtl/core/cdc_strobe_data.v Normal file
View File

@@ -0,0 +1,130 @@
`timescale 1 ns/1 ps
// =============================================================================
// cdc_strobe_data
// - One-deep mailbox for (strobe + data) crossing clock domains.
// - Uses toggle req/ack with 2FF sync for toggles.
// - Wide bus is held stable by source until ack, destination samples-until-stable.
// =============================================================================
module cdc_strobe_data #(
parameter integer WIDTH = 32,
parameter integer STABLE_SAMPLES = 2 // >=2 recommended
)(
// Source domain
input wire s_clk,
input wire s_rst, // async OK (posedge) if used consistently
input wire s_pulse, // strobe (1+ cycles). Accepted when not busy.
input wire [WIDTH-1:0] s_data,
output wire s_busy, // 1 = mailbox full / waiting for ack
output wire s_accepted, // 1-cycle pulse when we accepted s_pulse
// Destination domain
input wire d_clk,
input wire d_rst,
output reg d_pulse, // 1-cycle pulse on new data
output reg [WIDTH-1:0] d_data // updated when d_pulse asserted; held otherwise
);
// ----------------------------
// Source: hold + req toggle
// ----------------------------
reg [WIDTH-1:0] s_hold;
reg s_req_tog;
reg s_inflight;
// Ack toggle synchronized into source domain
(* ASYNC_REG="TRUE" *) reg s_ack_sync1, s_ack_sync2;
assign s_busy = s_inflight;
wire do_accept = s_pulse && !s_inflight;
assign s_accepted = do_accept;
// d_ack_tog is generated in destination domain (declared below as reg)
// and is synced here with 2FF.
always @(posedge s_clk or posedge s_rst) begin
if (s_rst) begin
s_hold <= {WIDTH{1'b0}};
s_req_tog <= 1'b0;
s_inflight <= 1'b0;
s_ack_sync1 <= 1'b0;
s_ack_sync2 <= 1'b0;
end else begin
s_ack_sync1 <= d_ack_tog;
s_ack_sync2 <= s_ack_sync1;
// clear inflight when ack matches current req toggle
if (s_inflight && (s_ack_sync2 == s_req_tog))
s_inflight <= 1'b0;
// accept new item
if (do_accept) begin
s_hold <= s_data;
s_req_tog <= ~s_req_tog;
s_inflight <= 1'b1;
end
end
end
// ----------------------------
// Destination: sync req toggle, sample-until-stable, then ack toggle
// ----------------------------
(* ASYNC_REG="TRUE" *) reg d_req_sync1, d_req_sync2;
reg d_req_seen;
reg d_ack_tog;
reg [WIDTH-1:0] samp;
reg [WIDTH-1:0] samp_prev;
integer stable_cnt;
reg capturing;
wire d_new_req = (d_req_sync2 != d_req_seen);
always @(posedge d_clk or posedge d_rst) begin
if (d_rst) begin
d_req_sync1 <= 1'b0;
d_req_sync2 <= 1'b0;
d_req_seen <= 1'b0;
d_ack_tog <= 1'b0;
d_pulse <= 1'b0;
d_data <= {WIDTH{1'b0}};
samp <= {WIDTH{1'b0}};
samp_prev <= {WIDTH{1'b0}};
stable_cnt <= 0;
capturing <= 1'b0;
end else begin
d_pulse <= 1'b0;
d_req_sync1 <= s_req_tog;
d_req_sync2 <= d_req_sync1;
if (d_new_req && !capturing) begin
capturing <= 1'b1;
stable_cnt <= 0;
samp_prev <= s_hold;
samp <= s_hold;
end else if (capturing) begin
samp <= s_hold;
if (samp == samp_prev) begin
if (stable_cnt < (STABLE_SAMPLES-1))
stable_cnt <= stable_cnt + 1;
else begin
// accept
d_data <= samp;
d_pulse <= 1'b1;
d_req_seen <= d_req_sync2;
d_ack_tog <= ~d_ack_tog;
capturing <= 1'b0;
end
end else begin
stable_cnt <= 0;
end
samp_prev <= samp;
end
end
end
endmodule

View File

@@ -48,20 +48,18 @@ module top_jtag(
);
wire [31:0] gpio;
wire [31:0] gpio_in;
assign gpio_in = 32'h0;
wb_gpio #(
.address(32'h00000000)
) u_wb_gpio (
.i_wb_clk(clk_15),
.i_wb_rst(i_rst | cmd_reset),
.i_wb_rst(i_rst),
.i_wb_adr(wb_adr),
.i_wb_dat(wb_dat),
.i_wb_sel(wb_sel),
.i_wb_we(wb_we),
.i_wb_stb(wb_stb & wb_cyc),
.i_gpio(gpio_in),
.i_gpio(gpio),
.o_wb_rdt(wb_rdt),
.o_wb_ack(wb_ack),
.o_gpio(gpio)
@@ -69,7 +67,7 @@ module top_jtag(
assign LED = gpio[7:0];
assign r2r = gpio[13:8];
assign led_green = gpio[30];
assign led_red = gpio[31];
assign led_green = cmd_reset;
assign led_red = 'b0;
endmodule

View File

@@ -1,193 +1,500 @@
`timescale 1ns/1ps
`timescale 1 ns/1 ps
module jtag_wb_bridge #(
parameter integer chain = 1,
// 0: Use cmd_addr[1:0] to select byte lane on 32-bit WB data bus.
// 1: Always use lane 0 (LSB), for byte-wide memories that return data in [7:0].
parameter integer byte_aligned = 0
parameter integer chain = 1,
// 0: use addr[1:0] for byte lane on 32-bit WB
// 1: always use lane 0
parameter integer byte_aligned = 0
)(
input wire i_clk,
input wire i_rst,
input wire i_clk,
input wire i_rst,
output wire [31:0] o_wb_adr,
output wire [31:0] o_wb_dat,
output wire [3:0] o_wb_sel,
output wire o_wb_we,
output wire o_wb_cyc,
output wire o_wb_stb,
input wire [31:0] i_wb_rdt,
input wire i_wb_ack,
output wire [31:0] o_wb_adr,
output wire [31:0] o_wb_dat,
output wire [3:0] o_wb_sel,
output wire o_wb_we,
output wire o_wb_cyc,
output wire o_wb_stb,
input wire [31:0] i_wb_rdt,
input wire i_wb_ack,
output wire o_cmd_reset
output wire o_cmd_reset
);
// 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;
wire [41:0] jtag_data_in;
wire jtag_async_reset;
// ===========================================================================
// JTAG interface (Spartan-6 BSCAN wrapper)
// ===========================================================================
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;
jtag_if #(
.chain(chain)
) 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)
);
// 48-bit DR (symmetrical command/response)
reg [47:0] jtag_shreg;
assign jtag_async_reset = jtag_reset || i_rst;
jtag_if #(
.chain(chain)
) u_jtag (
.i_tdo(jtag_shreg[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)
);
// JTAG shift register behavior
always @(posedge jtag_drck or posedge jtag_async_reset) begin
if (jtag_async_reset) begin
jtag_q <= 42'b0;
end else if (jtag_sel && jtag_capture) begin
jtag_q <= jtag_data_in;
end else if (jtag_sel && jtag_shift) begin
jtag_q <= {jtag_tdi, jtag_q[41:1]};
end
wire jtag_async_reset = jtag_reset || i_rst;
// ===========================================================================
// CDC request/response channel (48/48 symmetric)
// Side A: JTAG/TCK domain
// Side B: system/i_clk domain
// ===========================================================================
wire a_req_busy;
wire a_req_accepted;
wire a_resp_pulse;
wire [47:0] a_resp_data;
wire b_req_pulse;
wire [47:0] b_req_data;
reg b_resp_pulse;
reg [47:0] b_resp_data;
wire b_resp_busy;
wire b_resp_accepted;
// Accept UPDATE as a request strobe (qualified by SEL and !busy)
wire a_req_pulse = jtag_sel && jtag_update && !a_req_busy;
wire [47:0] a_req_data = jtag_shreg;
cdc_req_resp #(
.REQ_W(48),
.RESP_W(48),
.STABLE_SAMPLES(2)
) u_cdc (
.a_clk(jtag_tck),
.a_rst(jtag_async_reset),
.a_req_pulse(a_req_pulse),
.a_req_data(a_req_data),
.a_req_busy(a_req_busy),
.a_req_accepted(a_req_accepted),
.a_resp_pulse(a_resp_pulse),
.a_resp_data(a_resp_data),
.b_clk(i_clk),
.b_rst(i_rst),
.b_req_pulse(b_req_pulse),
.b_req_data(b_req_data),
.b_resp_pulse(b_resp_pulse),
.b_resp_data(b_resp_data),
.b_resp_busy(b_resp_busy),
.b_resp_accepted(b_resp_accepted)
);
// ===========================================================================
// JTAG/TCK domain shift/capture
// ===========================================================================
reg [47:0] resp_hold_tck;
always @(posedge jtag_tck or posedge jtag_async_reset) begin
if (jtag_async_reset) begin
jtag_shreg <= 48'd0;
resp_hold_tck <= 48'd0;
end else begin
// Latch new response word from CDC when it arrives (independent of CAPTURE)
if (a_resp_pulse) begin
resp_hold_tck <= a_resp_data;
end
if (jtag_sel && jtag_capture) begin
// Load response into shift register for host readout
jtag_shreg <= resp_hold_tck;
end else if (jtag_sel && jtag_shift) begin
// Shift: MSB in, LSB out to TDO
jtag_shreg <= {jtag_tdi, jtag_shreg[47:1]};
end
end
end
// -----------------------------------------------------------------------------
// JTAG -> i_clk crossing using toggle request/ack handshake.
// Command packet format: [41]=we, [40]=reset, [39:8]=addr, [7:0]=wdata
// -----------------------------------------------------------------------------
reg [41:0] j_cmd_hold;
reg j_req_tgl;
reg j_ack_sync_1;
reg j_ack_sync_2;
// ===========================================================================
// System domain: Wishbone master + small command queue + response pending
// ===========================================================================
// Opcodes
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;
localparam [7:0] OP_CLEAR_FLAGS = 8'h40;
reg s_ack_tgl;
reg s_req_sync_1;
reg s_req_sync_2;
reg s_req_sync_3;
reg [41:0] s_cmd_sync_1;
reg [41:0] s_cmd_sync_2;
// Wishbone regs
reg wb_busy;
reg [31:0] wb_adr_r;
reg [31:0] wb_dat_r;
reg [3:0] wb_sel_r;
reg wb_we_r;
always @(posedge jtag_drck or posedge jtag_async_reset) begin
if (jtag_async_reset) begin
j_ack_sync_1 <= 1'b0;
j_ack_sync_2 <= 1'b0;
assign o_wb_adr = wb_adr_r;
assign o_wb_dat = wb_dat_r;
assign o_wb_sel = wb_sel_r;
assign o_wb_we = wb_we_r;
assign o_wb_cyc = wb_busy;
assign o_wb_stb = wb_busy;
// Reset control
reg cmd_reset_level_r;
assign o_cmd_reset = cmd_reset_level_r;
// For reporting only: sync a_req_busy (TCK domain) into i_clk
(* ASYNC_REG="TRUE" *) reg req_busy_sync1, req_busy_sync2;
wire req_busy_tck_sync = req_busy_sync2;
// Sequencing
reg [7:0] cmd_seq_r;
reg [7:0] resp_seq_r;
// Sticky flags (cleared by CLEAR_FLAGS or reset)
reg flag_cmd_overflow;
reg flag_illegal;
reg flag_wb_busy_at_req;
// Snapshot info
reg last_we_r;
reg [7:0] last_opcode_r;
// Active command / queued command
reg act_valid;
reg [7:0] act_opcode;
reg [31:0] act_addr;
reg [7:0] act_data;
reg [7:0] act_seq;
reg q_valid;
reg [7:0] q_opcode;
reg [31:0] q_addr;
reg [7:0] q_data;
reg [7:0] q_seq;
// Response pending buffer (to avoid dropping if resp mailbox busy)
reg resp_pending;
reg [47:0] resp_pending_word;
// Lane selection
wire [1:0] addr_lane = byte_aligned ? 2'b00 : act_addr[1:0];
// Helpers: form SEL/DAT for byte write
function [3:0] sel_from_lane(input [1:0] lane);
case (lane)
2'b00: sel_from_lane = 4'b0001;
2'b01: sel_from_lane = 4'b0010;
2'b10: sel_from_lane = 4'b0100;
default: sel_from_lane = 4'b1000;
endcase
endfunction
function [31:0] dat_from_lane_byte(input [1:0] lane, input [7:0] b);
case (lane)
2'b00: dat_from_lane_byte = {24'b0, b};
2'b01: dat_from_lane_byte = {16'b0, b, 8'b0};
2'b10: dat_from_lane_byte = {8'b0, b, 16'b0};
default: dat_from_lane_byte = {b, 24'b0};
endcase
endfunction
function [7:0] byte_from_lane(input [1:0] lane, input [31:0] w);
case (lane)
2'b00: byte_from_lane = w[7:0];
2'b01: byte_from_lane = w[15:8];
2'b10: byte_from_lane = w[23:16];
default: byte_from_lane = w[31:24];
endcase
endfunction
// Build response word
function [47:0] pack_resp(
input [7:0] resp_seq,
input [7:0] status,
input [7:0] cmd_seq,
input [7:0] data,
input [7:0] flags,
input [7:0] last_op
);
pack_resp = {resp_seq, status, cmd_seq, data, flags, last_op};
endfunction
// STATUS bits (snapshot)
wire [7:0] status_snapshot = {
2'b00, // [7:6]
1'b1, // [5] resp_valid
last_we_r, // [4] last_we
cmd_reset_level_r, // [3] reset_level
b_resp_busy, // [2] resp_busy (system domain)
req_busy_tck_sync, // [1] req_busy (synced from TCK just for reporting)
wb_busy // [0] wb_busy
};
// FLAGS bits (sticky)
wire [7:0] flags_sticky = {
4'b0000, // [7:4] reserved
1'b0, // [3] reserved
flag_wb_busy_at_req, // [2]
flag_illegal, // [1]
flag_cmd_overflow // [0]
};
// Queue a command (or set overflow sticky if queue full)
task automatic enqueue_cmd(
input [7:0] op,
input [31:0] addr,
input [7:0] dat,
input [7:0] seq
);
begin
if (!q_valid) begin
q_valid <= 1'b1;
q_opcode <= op;
q_addr <= addr;
q_data <= dat;
q_seq <= seq;
end else begin
// Already have one queued; mark overflow and drop this command
flag_cmd_overflow <= 1'b1;
end
end
endtask
// Start executing a command (for non-WB ops may immediately create a response)
task automatic start_active_cmd(
input [7:0] cmd_opcode,
input [31:0] cmd_addr,
input [7:0] cmd_data,
input [7:0] cmd_seq
);
reg [1:0] cmd_addr_lane;
begin
cmd_addr_lane = byte_aligned ? 2'b00 : cmd_addr[1:0];
last_opcode_r <= cmd_opcode;
last_we_r <= (cmd_opcode == OP_WRITE8);
// If we're already mid-flight or holding a response, note it (diagnostic)
if (wb_busy || resp_pending)
flag_wb_busy_at_req <= 1'b1;
case (cmd_opcode)
OP_NOP: begin
// immediate response
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_PING: begin
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'hA5, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_CLEAR_FLAGS: begin
flag_cmd_overflow <= 1'b0;
flag_illegal <= 1'b0;
flag_wb_busy_at_req <= 1'b0;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, 8'h00, cmd_opcode);
resp_pending <= 1'b1;
end
OP_RESET_ON: begin
cmd_reset_level_r <= 1'b1;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_RESET_OFF: begin
cmd_reset_level_r <= 1'b0;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
OP_WRITE8: begin
// launch WB write (byte)
wb_busy <= 1'b1;
wb_we_r <= 1'b1;
wb_adr_r <= cmd_addr;
wb_sel_r <= sel_from_lane(cmd_addr_lane);
wb_dat_r <= dat_from_lane_byte(cmd_addr_lane, cmd_data);
end
OP_READ8: begin
// launch WB read (byte select)
wb_busy <= 1'b1;
wb_we_r <= 1'b0;
wb_adr_r <= cmd_addr;
wb_sel_r <= sel_from_lane(cmd_addr_lane);
wb_dat_r <= 32'b0;
end
default: begin
flag_illegal <= 1'b1;
resp_pending_word <= pack_resp(resp_seq_r, status_snapshot, cmd_seq, 8'h00, flags_sticky, cmd_opcode);
resp_pending <= 1'b1;
end
endcase
end
endtask
// System main
always @(posedge i_clk) begin
if (i_rst) begin
wb_busy <= 1'b0;
wb_adr_r <= 32'b0;
wb_dat_r <= 32'b0;
wb_sel_r <= 4'b0000;
wb_we_r <= 1'b0;
cmd_reset_level_r<= 1'b0;
req_busy_sync1 <= 1'b0;
req_busy_sync2 <= 1'b0;
cmd_seq_r <= 8'd0;
resp_seq_r <= 8'd0;
flag_cmd_overflow<= 1'b0;
flag_illegal <= 1'b0;
flag_wb_busy_at_req <= 1'b0;
last_we_r <= 1'b0;
last_opcode_r <= 8'h00;
act_valid <= 1'b0;
act_opcode <= 8'h00;
act_addr <= 32'h0;
act_data <= 8'h00;
act_seq <= 8'h00;
q_valid <= 1'b0;
q_opcode <= 8'h00;
q_addr <= 32'h0;
q_data <= 8'h00;
q_seq <= 8'h00;
resp_pending <= 1'b0;
resp_pending_word<= 48'h0;
b_resp_pulse <= 1'b0;
b_resp_data <= 48'h0;
end else begin
b_resp_pulse <= 1'b0;
// Sync req-busy level (reporting only)
req_busy_sync1 <= a_req_busy;
req_busy_sync2 <= req_busy_sync1;
// -----------------------------------------------------------------------
// Accept incoming command from CDC (always delivered; we buffer internally)
// -----------------------------------------------------------------------
if (b_req_pulse) begin
// assign a sequence number to each received command
cmd_seq_r <= cmd_seq_r + 8'd1;
// If we can start immediately (no active, no wb, no pending response), do so.
if (!act_valid && !wb_busy && !resp_pending) begin
act_valid <= 1'b1;
act_opcode <= b_req_data[47:40];
act_addr <= b_req_data[39:8];
act_data <= b_req_data[7:0];
act_seq <= cmd_seq_r;
// Start it right away
start_active_cmd(b_req_data[47:40], b_req_data[39:8], b_req_data[7:0], cmd_seq_r);
end else begin
j_ack_sync_1 <= s_ack_tgl;
j_ack_sync_2 <= j_ack_sync_1;
// Otherwise enqueue one-deep
enqueue_cmd(b_req_data[47:40], b_req_data[39:8], b_req_data[7:0], cmd_seq_r);
end
end
end
always @(posedge jtag_update or posedge jtag_async_reset) begin
if (jtag_async_reset) begin
j_cmd_hold <= 42'b0;
j_req_tgl <= 1'b0;
end else if (jtag_sel && (j_ack_sync_2 == j_req_tgl)) begin
j_cmd_hold <= jtag_q;
j_req_tgl <= ~j_req_tgl;
end
end
// -----------------------------------------------------------------------
// Wishbone completion -> create response (but don't drop; buffer pending)
// -----------------------------------------------------------------------
if (wb_busy && i_wb_ack) begin
wb_busy <= 1'b0;
wb_we_r <= 1'b0;
// -----------------------------------------------------------------------------
// Wishbone classic single-request master (1 outstanding transaction max).
// -----------------------------------------------------------------------------
reg wb_busy;
reg [31:0] wb_adr_r;
reg [31:0] wb_dat_r;
reg [3:0] wb_sel_r;
reg wb_we_r;
reg cmd_reset_level_r;
reg [31:0] resp_addr_r;
reg [7:0] resp_data_r;
wire req_pulse;
wire [7:0] cmd_wdata;
wire [31:0] cmd_addr;
wire cmd_reset;
wire cmd_we;
wire [1:0] req_lane;
wire [1:0] resp_lane;
assign req_pulse = s_req_sync_2 ^ s_req_sync_3;
assign cmd_wdata = s_cmd_sync_2[7:0];
assign cmd_addr = s_cmd_sync_2[39:8];
assign cmd_reset = s_cmd_sync_2[40];
assign cmd_we = s_cmd_sync_2[41];
assign req_lane = byte_aligned ? 2'b00 : cmd_addr[1:0];
assign resp_lane = byte_aligned ? 2'b00 : wb_adr_r[1:0];
assign o_wb_adr = wb_adr_r;
assign o_wb_dat = wb_dat_r;
assign o_wb_sel = wb_sel_r;
assign o_wb_we = wb_we_r;
assign o_wb_cyc = wb_busy;
assign o_wb_stb = wb_busy;
assign o_cmd_reset = cmd_reset_level_r;
always @(posedge i_clk) begin
if (i_rst) begin
s_ack_tgl <= 1'b0;
s_req_sync_1 <= 1'b0;
s_req_sync_2 <= 1'b0;
s_req_sync_3 <= 1'b0;
s_cmd_sync_1 <= 42'b0;
s_cmd_sync_2 <= 42'b0;
wb_busy <= 1'b0;
wb_adr_r <= 32'b0;
wb_dat_r <= 32'b0;
wb_sel_r <= 4'b0000;
wb_we_r <= 1'b0;
cmd_reset_level_r <= 1'b0;
resp_addr_r <= 32'b0;
resp_data_r <= 8'b0;
// Determine response byte
if (act_opcode == OP_READ8) begin
resp_pending_word <= pack_resp(
resp_seq_r,
status_snapshot,
act_seq,
byte_from_lane(addr_lane, i_wb_rdt),
flags_sticky,
act_opcode
);
end else begin
s_req_sync_1 <= j_req_tgl;
s_req_sync_2 <= s_req_sync_1;
s_req_sync_3 <= s_req_sync_2;
s_cmd_sync_1 <= j_cmd_hold;
s_cmd_sync_2 <= s_cmd_sync_1;
if (req_pulse && !wb_busy) begin
wb_busy <= 1'b1;
wb_we_r <= cmd_we;
wb_adr_r <= cmd_addr;
cmd_reset_level_r <= cmd_reset;
case (req_lane)
2'b00: begin wb_sel_r <= 4'b0001; wb_dat_r <= {24'b0, cmd_wdata}; end
2'b01: begin wb_sel_r <= 4'b0010; wb_dat_r <= {16'b0, cmd_wdata, 8'b0}; end
2'b10: begin wb_sel_r <= 4'b0100; wb_dat_r <= {8'b0, cmd_wdata, 16'b0}; end
default: begin wb_sel_r <= 4'b1000; wb_dat_r <= {cmd_wdata, 24'b0}; end
endcase
end
if (wb_busy && i_wb_ack) begin
wb_busy <= 1'b0;
wb_we_r <= 1'b0;
resp_addr_r <= wb_adr_r;
case (resp_lane)
2'b00: resp_data_r <= i_wb_rdt[7:0];
2'b01: resp_data_r <= i_wb_rdt[15:8];
2'b10: resp_data_r <= i_wb_rdt[23:16];
default: resp_data_r <= i_wb_rdt[31:24];
endcase
s_ack_tgl <= s_req_sync_2;
end
// WRITE8: echo written byte (lightweight)
resp_pending_word <= pack_resp(
resp_seq_r,
status_snapshot,
act_seq,
act_data,
flags_sticky,
act_opcode
);
end
end
resp_pending <= 1'b1;
end
assign jtag_data_in = {2'b00, resp_addr_r, resp_data_r};
// -----------------------------------------------------------------------
// If we have a pending response and response mailbox is free, send it
// -----------------------------------------------------------------------
if (resp_pending && !b_resp_busy) begin
b_resp_data <= resp_pending_word;
b_resp_pulse <= 1'b1;
resp_pending <= 1'b0;
resp_seq_r <= resp_seq_r + 8'd1;
// Mark active command complete
act_valid <= 1'b0;
// If there is a queued command, promote and start it
if (q_valid) begin
act_valid <= 1'b1;
act_opcode <= q_opcode;
act_addr <= q_addr;
act_data <= q_data;
act_seq <= q_seq;
q_valid <= 1'b0;
start_active_cmd(q_opcode, q_addr, q_data, q_seq);
end
end
// -----------------------------------------------------------------------
// If no active command but there is a queued one (and we're not busy), start it
// -----------------------------------------------------------------------
if (!act_valid && q_valid && !wb_busy && !resp_pending) begin
act_valid <= 1'b1;
act_opcode <= q_opcode;
act_addr <= q_addr;
act_data <= q_data;
act_seq <= q_seq;
q_valid <= 1'b0;
start_active_cmd(q_opcode, q_addr, q_data, q_seq);
end
end
end
endmodule