// retroDE_ps2 — tb_gs_axi_w_regbuf (Ch441)
//
// Focused scoreboard for the one-entry fully-registered AXI W buffer. Verifies:
//   (1) EXACTLY-ONCE, IN-ORDER delivery with payload integrity {WDATA,WSTRB,WLAST}
//       under randomized upstream offer + downstream backpressure (a drop, dup, or
//       reorder trips the sequence scoreboard).
//   (2) The NO-COMBINATIONAL-BYPASS contract: u_wready === !full every cycle, so a
//       fall-through `u_wready = !full || d_wready` (which would leak downstream
//       WREADY back upstream into the Z FSM) is caught. A directed phase forces the
//       full && d_wready case.
//   (3) Downstream payload held STABLE while d_wvalid && !d_wready.
//   (4) Full drain leaves the buffer empty with equal produced/consumed counts.
`timescale 1ns/1ps

module tb_gs_axi_w_regbuf;
    localparam int WD = 256, WS = 32;
    logic clk = 0; always #5 clk = ~clk;   // 100 MHz
    logic rst_n;

    logic [WD-1:0] u_wdata; logic [WS-1:0] u_wstrb; logic u_wlast, u_wvalid, u_wready;
    logic [WD-1:0] d_wdata; logic [WS-1:0] d_wstrb; logic d_wlast, d_wvalid; logic d_wready;

    gs_axi_w_regbuf #(.WDATA_W(WD), .WSTRB_W(WS)) dut (
        .clk(clk), .rst_n(rst_n),
        .u_wdata(u_wdata), .u_wstrb(u_wstrb), .u_wlast(u_wlast), .u_wvalid(u_wvalid), .u_wready(u_wready),
        .d_wdata(d_wdata), .d_wstrb(d_wstrb), .d_wlast(d_wlast), .d_wvalid(d_wvalid), .d_wready(d_wready)
    );

    int errors; initial errors = 0;

    // distinct nonzero payload per sequence value (fills all 256 + 32 bits)
    function automatic logic [WD-1:0] mk(input logic [31:0] s);
        mk = {s^32'hDEADBEEF, s+32'd5, ~s, s^32'hA5A5A5A5, s+32'd3, s^32'h0F0F0F0F, s+32'd1, s};
    endfunction
    function automatic logic [WS-1:0] mk_strb(input logic [31:0] s);
        mk_strb = (s ^ 32'hFFFF0000) | 32'd1;   // nonzero, varies with s
    endfunction

    // LFSR backpressure on both sides
    logic [15:0] ul = 16'hACE1, dl = 16'h1357;
    always_ff @(posedge clk) begin
        ul <= {ul[14:0], ul[15]^ul[13]^ul[12]^ul[10]};
        dl <= {dl[14:0], dl[15]^dl[13]^dl[12]^dl[10]};
    end
    logic force_ready, force_stall, prod_freeze;
    assign d_wready = force_ready ? 1'b1 : (force_stall ? 1'b0 : (dl[0] | dl[3]));

    // AXI-legal producer: assert u_wvalid with STABLE payload until accepted.
    logic [31:0] wr_seq;   // beats accepted UPSTREAM (into the buffer)
    logic        pending;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin wr_seq <= 0; pending <= 1'b0; end
        else if (u_wvalid && u_wready) begin
            wr_seq  <= wr_seq + 1;
            pending <= (ul[0] | ul[3]) && !prod_freeze;   // maybe offer the next beat
        end
        else if (!pending) pending <= (ul[0] | ul[3]) && !prod_freeze;
    end
    assign u_wvalid = pending;
    assign u_wdata  = mk(wr_seq);        // stable while pending (wr_seq only advances on accept)
    assign u_wstrb  = mk_strb(wr_seq);
    assign u_wlast  = 1'b1;              // single-beat writes

    // (1) downstream scoreboard: exactly-once, in-order, payload-correct
    logic [31:0] rd_seq;   // beats delivered DOWNSTREAM
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) rd_seq <= 0;
        else if (d_wvalid && d_wready) begin
            if (d_wdata !== mk(rd_seq) || d_wstrb !== mk_strb(rd_seq) || d_wlast !== 1'b1) begin
                if (errors < 20) $error("[wbuf] drop/dup/reorder/payload at seq %0d: wdata %h strb %h last %b",
                                        rd_seq, d_wdata, d_wstrb, d_wlast);
                errors++;
            end
            rd_seq <= rd_seq + 1;
        end
    end

    // (2) NO combinational downstream-ready bypass: u_wready must equal !full.
    always_ff @(posedge clk) if (rst_n) begin
        if (u_wready !== !dut.full) begin
            if (errors < 20) $error("[wbuf] u_wready(%b) != !full(%b) — combinational bypass?", u_wready, dut.full);
            errors++;
        end
    end
    // Coverage: prove the distinguishing full && d_wready case was actually hit;
    // otherwise the no-bypass check above never exercised its meaningful case.
    logic saw_full_and_ready; initial saw_full_and_ready = 1'b0;
    always_ff @(posedge clk) if (rst_n && dut.full && d_wready) saw_full_and_ready <= 1'b1;

    // (3) while stalled (prev cycle d_wvalid && !d_wready), the SAME beat must still
    //     be presented: d_wvalid asserted AND {d_wdata,d_wstrb,d_wlast} unchanged.
    logic [WD-1:0] hold_d; logic [WS-1:0] hold_s; logic hold_l; logic hold_v;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin hold_v <= 1'b0; hold_d <= '0; hold_s <= '0; hold_l <= 1'b0; end
        else begin
            if (hold_v) begin
                if (!d_wvalid) begin
                    if (errors < 20) $error("[wbuf] d_wvalid deasserted while stalled"); errors++;
                end else if (d_wdata !== hold_d || d_wstrb !== hold_s || d_wlast !== hold_l) begin
                    if (errors < 20) $error("[wbuf] downstream {wdata,wstrb,wlast} changed while stalled"); errors++;
                end
            end
            hold_v <= d_wvalid && !d_wready;
            hold_d <= d_wdata;
            hold_s <= d_wstrb;
            hold_l <= d_wlast;
        end
    end

    initial begin
        rst_n = 0; force_ready = 0; force_stall = 0; prod_freeze = 0;
        repeat (6) @(posedge clk); rst_n = 1;
        @(posedge clk);
        if (d_wvalid !== 1'b0) begin $error("[wbuf] not empty after reset"); errors++; end

        // Phase 1: randomized offer + backpressure
        repeat (20000) @(posedge clk);

        // Phase 2 (directed): stall downstream so the buffer fills and STAYS full
        // (u_wready must read 0 = !full), then hold full while d_wready=1 — this is
        // the full && d_wready case a fall-through skid would mishandle.
        force_stall = 1; repeat (200) @(posedge clk);
        force_stall = 0; force_ready = 1; repeat (200) @(posedge clk);
        force_ready = 0;

        // Phase 3: freeze producer, drain fully
        prod_freeze = 1; force_ready = 1;
        begin int g; g = 0; while ((wr_seq !== rd_seq) && g < 4000) begin @(posedge clk); g++; end end
        repeat (10) @(posedge clk);

        if (d_wvalid !== 1'b0) begin $error("[wbuf] not empty after drain (d_wvalid=%b)", d_wvalid); errors++; end
        if (wr_seq !== rd_seq) begin $error("[wbuf] count mismatch: in %0d out %0d", wr_seq, rd_seq); errors++; end
        if (wr_seq < 32'd2000) begin $error("[wbuf] too few transfers (%0d) — not meaningful", wr_seq); errors++; end
        if (!saw_full_and_ready) begin $error("[wbuf] coverage: full && d_wready never observed — no-bypass case unexercised"); errors++; end

        $display("[tb_gs_axi_w_regbuf] in=%0d out=%0d errors=%0d", wr_seq, rd_seq, errors);
        if (errors == 0) $display("[tb_gs_axi_w_regbuf] PASS");
        else             $display("[tb_gs_axi_w_regbuf] FAIL");
        $finish;
    end

    initial begin #2000000; $error("[tb_gs_axi_w_regbuf] TIMEOUT"); $finish; end
endmodule : tb_gs_axi_w_regbuf
