// retroDE_ps2 — tb_gs_lpddr_axi_master_elastic (Ch357, Codex)
//
// Saturation / producer-backpressure test for gs_lpddr_axi_master with ELASTIC_BACKPRESSURE=1
// (the u_zc_emit|u_c configuration). Proves the elastic (skid) stage + px_ready handshake:
//   * a backpressure-aware producer HOLDS px_emit until px_ready and NEVER loses a pixel;
//   * FIFO saturation (force_stall fills the FIFO -> px_ready drops -> producer stalls) drops nothing;
//   * simultaneous stage drain+refill (intermittent AXI wready) preserves every beat;
//   * a partial beat + ordered EOF marker asserted WHILE the FIFO is full retry until accepted;
//   * randomized AW/W/B stalls; exact slave-memory contents == replayed reference; col_ovf == 0;
//   * frame_drained eventually asserts (no hang).

`timescale 1ns/1ps

module tb_gs_lpddr_axi_master_elastic;
    localparam int MEM_WORDS = 4096;               // 16 KiB byte memory
    localparam int NPIX      = 600;                // pixels driven (multiple beats + a trailing partial)

    logic gs_clk=0;  always #5  gs_clk =~gs_clk;    // 100 MHz
    logic axi_clk=0; always #7  axi_clk=~axi_clk;   // ~71 MHz, async
    logic gs_rst_n, axi_rst_n;
    logic ctrl_commit=0; always #13 ctrl_commit=~ctrl_commit;

    // ---- producer (backpressure-aware) ----
    logic        px_emit;
    logic [31:0] px_addr, px_pix;
    logic        px_ready;
    logic        flush;

    // ---- AXI ----
    logic [31:0] awaddr; logic [7:0] awlen; logic [2:0] awsize; logic [1:0] awburst; logic [4:0] awid;
    logic        awvalid, awready;
    logic [255:0] wdata; logic [31:0] wstrb; logic wlast, wvalid, wready;
    logic        bvalid, bready; logic [1:0] bresp;
    logic [31:0] beats_written, bursts_issued, bresp_err_count, col_ovf;
    logic        idle, frame_drained;

    logic force_stall = 1'b0;                       // hard AXI-off window to fill the FIFO
    logic [15:0] lfsr;
    always_ff @(posedge axi_clk or negedge axi_rst_n)
        if (!axi_rst_n) lfsr<=16'hACE1;
        else            lfsr<={lfsr[14:0], lfsr[15]^lfsr[13]^lfsr[12]^lfsr[10]};
    assign awready = !force_stall && lfsr[0];
    assign wready  = !force_stall && (lfsr[2] | lfsr[5]);

    // delayed B response
    logic [2:0] b_pending;
    always_ff @(posedge axi_clk or negedge axi_rst_n) begin
        if (!axi_rst_n) begin bvalid<=0; b_pending<=0; end
        else begin
            if (wvalid && wready)          b_pending <= 3'd3;
            else if (b_pending != 0)       b_pending <= b_pending - 3'd1;
            if (b_pending == 3'd1)         bvalid <= 1;
            else if (bvalid && bready)     bvalid <= 0;
        end
    end
    assign bresp = 2'b00;

    gs_lpddr_axi_master #(.FIFO_DEPTH(16), .PIX_BYTES(4), .ELASTIC_BACKPRESSURE(1'b1)) dut (
        .gs_clk(gs_clk), .gs_rst_n(gs_rst_n), .enable(1'b1),
        .arm(1'b1), .canary(1'b0), .fb_base(32'h0), .ctrl_commit(ctrl_commit),
        .px_emit(px_emit), .px_addr(px_addr), .px_pix32(px_pix), .px_ready(px_ready), .flush(flush),
        .axi_clk(axi_clk), .axi_rst_n(axi_rst_n),
        .awaddr(awaddr), .awlen(awlen), .awsize(awsize), .awburst(awburst), .awid(awid),
        .awvalid(awvalid), .awready(awready),
        .wdata(wdata), .wstrb(wstrb), .wlast(wlast), .wvalid(wvalid), .wready(wready),
        .bvalid(bvalid), .bready(bready), .bresp(bresp),
        .beats_written(beats_written), .bursts_issued(bursts_issued),
        .bresp_err_count(bresp_err_count), .fifo_overflow_count(col_ovf), .idle(idle), .frame_drained(frame_drained)
    );

    // pixel stream: 40 px/line (not a beat multiple -> partials + periodic line-change flushes), 1 KiB line stride.
    function automatic [31:0] pix_addr(input int i);
        pix_addr = ((i/40) * 32'h400) + ((i%40) * 4);
    endfunction
    function automatic [31:0] pix_data(input int i);
        pix_data = 32'hC0DE0000 | (i & 32'h0000FFFF);
    endfunction

    // ---- capturing AXI slave (byte memory) — smem is written ONLY here ----
    logic [7:0] smem [0:MEM_WORDS-1];
    logic [31:0] aw_latch;
    always @(posedge axi_clk) begin
        if (awvalid && awready) aw_latch <= awaddr;
        if (wvalid && wready) for (int i=0;i<32;i++) if (wstrb[i]) begin
            int a; a = int'(aw_latch) + i; if (a>=0 && a<MEM_WORDS) smem[a] <= wdata[i*8 +: 8];
        end
    end

    // ---- producer state ----
    int  pidx;                    // next pixel to present
    int  accepted;                // pixels accepted (px_emit && px_ready)
    logic px_ready_dropped;       // witness: backpressure engaged at least once

    logic stream_en = 1'b0;    // hold the stream until arm_gs (the DUT's 2-FF-synced arm) is high, else px0/px1 would
                               // be handshake-accepted while the packer (gated on arm_gs) still ignores them.
    assign px_emit = stream_en && (pidx < NPIX);
    assign px_addr = pix_addr(pidx);
    assign px_pix  = pix_data(pidx);

    always_ff @(posedge gs_clk or negedge gs_rst_n) begin
        if (!gs_rst_n) begin pidx<=0; accepted<=0; px_ready_dropped<=0; end
        else begin
            if (px_emit && !px_ready) px_ready_dropped <= 1'b1;   // stalled: proves backpressure
            if (px_emit &&  px_ready) begin pidx <= pidx + 1; accepted <= accepted + 1; end
        end
    end

    // ---- stimulus / checks ----
    int errors; initial errors=0;
    initial begin
        gs_rst_n=0; axi_rst_n=0; flush=0; force_stall=0;
        repeat (6) @(posedge axi_clk); axi_rst_n=1;
        repeat (4) @(posedge gs_clk);  gs_rst_n=1;
        repeat (4) @(posedge gs_clk);  stream_en=1;   // let arm_gs settle before feeding pixels

        // Saturation window: after ~120 pixels, hard-stall AXI so the FIFO fills and px_ready must drop.
        fork
            begin
                while (accepted < 120) @(posedge gs_clk);
                force_stall = 1'b1;                       // FIFO fills -> px_ready drops -> producer stalls
                repeat (200) @(posedge axi_clk);
                force_stall = 1'b0;                       // release; drain resumes, producer un-stalls
            end
        join_none

        // wait for all pixels accepted (must complete despite the stall)
        begin int guard; guard=0;
            while (accepted < NPIX && guard <= 400000) begin @(posedge gs_clk); guard++; end
            if (accepted < NPIX) begin $error("[elastic] producer HUNG (accepted=%0d/%0d)", accepted, NPIX); errors++; end
        end

        // partial-beat + EOF marker WHILE momentarily stalling again (partial->marker while full)
        force_stall = 1'b1; repeat (30) @(posedge axi_clk);
        @(posedge gs_clk); flush = 1'b1; @(posedge gs_clk); flush = 1'b0;   // end-of-scene flush requested during stall
        repeat (40) @(posedge axi_clk); force_stall = 1'b0;

        // wait for the frame to drain (partial + marker accepted, all BRESPs in)
        begin int guard; guard=0;
            while (!frame_drained && guard <= 400000) begin @(posedge axi_clk); guard++; end
            if (!frame_drained) begin $error("[elastic] frame_drained never asserted (drain HUNG)"); errors++; end
        end
        repeat (20) @(posedge axi_clk);

        // ---- checks ----
        if (accepted !== NPIX)     begin $error("[elastic] accepted=%0d != NPIX=%0d", accepted, NPIX); errors++; end
        if (col_ovf  !== 0)        begin $error("[elastic] col_ovf=%0d (overflow witness must be 0)", col_ovf); errors++; end
        if (bresp_err_count !== 0) begin $error("[elastic] bresp_err=%0d", bresp_err_count); errors++; end
        if (!px_ready_dropped)     begin $error("[elastic] backpressure never engaged (px_ready stayed high under stall)"); errors++; end

        // exact contents: replay every accepted pixel and compare its 4 bytes against the slave memory.
        for (int i=0;i<accepted;i++) for (int b=0;b<4;b++) begin
            int a; logic [31:0] wd; logic [7:0] want;
            a = int'(pix_addr(i)) + b; wd = pix_data(i); want = wd[b*8 +: 8];
            if (a>=0 && a<MEM_WORDS && smem[a] !== want) begin
                if (errors < 20) $error("[elastic] px%0d byte%0d @%0d: smem=%02x != %02x", i, b, a, smem[a], want);
                errors++;
            end
        end

        $display("[tb_gs_lpddr_axi_master_elastic] accepted=%0d beats=%0d bursts=%0d col_ovf=%0d bp_seen=%0b errors=%0d",
                 accepted, beats_written, bursts_issued, col_ovf, px_ready_dropped, errors);
        if (errors==0) $display("[tb_gs_lpddr_axi_master_elastic] PASS");
        else           $display("[tb_gs_lpddr_axi_master_elastic] FAIL");
        $finish;
    end

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