// retroDE_ps2 — tb_gs_lpddr_z_rmw  (Ch357 gate 1 unit TB — packed PSMZ16S LPDDR Z RMW engine)
//
// Proves the standalone Z RMW engine against a software golden model:
//   * clamp16 source (min(z,0xFFFF)) + GEQUAL + ZMSK (test always runs; write suppressed only when zmsk=1).
//   * packed 16-bit Z, single write-back cache line: same-beat / same-PIXEL RMW hazards forwarded (no stale read).
//   * cross-beat PERSISTENCE: revisit passes read back Z written earlier (evicted to LPDDR then refetched).
//   * RANDOM AXI backpressure on AR/R/AW/W/B and random p_ready backpressure -> assert ZERO drops/overflow, 0 BRESP errs.
//   * preclear to the GEQUAL clear value.
// Every fragment must produce exactly one result with p_pass == golden; final stored Z (LPDDR + the one dirty line) == golden.
`timescale 1ns/1ps

module tb_gs_lpddr_z_rmw;
    localparam int FB_PXW = 64, FB_H = 32;
    localparam int NPX = FB_PXW*FB_H;            // 2048
    localparam int NBEATS = (NPX+15)/16;         // 128
    localparam int BIW = $clog2(NBEATS);         // beat-index width for mem addressing
    localparam [31:0] ZBASE = 32'h0030_0000;
    localparam [15:0] Z_CLEAR = 16'h0000;
    localparam int NFRAG = 6000;

    logic clk=0; always #5 clk=~clk;
    logic rst_n;
    int errors; initial errors=0;

    // DUT I/O
    logic        enable, clear_start, clear_done, scene_flush, z_drained;
    logic        f_valid, f_ready; logic [11:0] f_x, f_y; logic [31:0] f_z; logic f_zmsk; logic [1:0] f_ztst;
    logic        p_valid, p_ready, p_pass; logic [11:0] p_x, p_y; logic [15:0] p_zq;
    logic [31:0] araddr; logic [7:0] arlen; logic [2:0] arsize; logic [1:0] arburst; logic arvalid, arready;
    logic [255:0] rdata; logic [1:0] rresp; logic rlast, rvalid, rready;
    logic [31:0] awaddr; logic [7:0] awlen; logic [2:0] awsize; logic [1:0] awburst; 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_read, beats_written, bresp_err; logic idle;

    gs_lpddr_z_rmw #(.ZBASE(ZBASE), .FB_PXW(FB_PXW), .FB_H(FB_H), .Z_CLEAR(Z_CLEAR)) dut (
        .clk(clk), .rst_n(rst_n), .enable(enable), .clear_start(clear_start), .clear_done(clear_done),
        .scene_flush(scene_flush), .z_drained(z_drained),
        .f_valid(f_valid), .f_ready(f_ready), .f_x(f_x), .f_y(f_y), .f_z(f_z), .f_zmsk(f_zmsk), .f_ztst(f_ztst),
        .p_valid(p_valid), .p_ready(p_ready), .p_pass(p_pass), .p_x(p_x), .p_y(p_y), .p_zq(p_zq),
        .araddr(araddr), .arlen(arlen), .arsize(arsize), .arburst(arburst), .arvalid(arvalid), .arready(arready),
        .rdata(rdata), .rresp(rresp), .rlast(rlast), .rvalid(rvalid), .rready(rready),
        .awaddr(awaddr), .awlen(awlen), .awsize(awsize), .awburst(awburst), .awvalid(awvalid), .awready(awready),
        .wdata(wdata), .wstrb(wstrb), .wlast(wlast), .wvalid(wvalid), .wready(wready),
        .bvalid(bvalid), .bready(bready), .bresp(bresp),
        .beats_read(beats_read), .beats_written(beats_written), .bresp_err(bresp_err), .idle(idle)
    );

    // ---------------- behavioral LPDDR (256-bit) with RANDOM backpressure ----------------
    logic [255:0] mem [0:NBEATS-1];
    logic [15:0]  lfsr=16'hACE1; always_ff @(posedge clk) lfsr<={lfsr[14:0], lfsr[15]^lfsr[13]^lfsr[12]^lfsr[10]};
    // read channel
    logic [31:0] rd_addr_l; logic rd_pending; logic [3:0] rd_dly;
    // write channel
    logic [31:0] wr_addr_l; logic [255:0] wr_data_l; logic aw_seen, w_seen; logic [3:0] b_dly; logic b_pending;
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; rd_pending<=0; rd_dly<=0;
            awready<=0; wready<=0; bvalid<=0; bresp<=0; aw_seen<=0; w_seen<=0; b_pending<=0; b_dly<=0;
        end else begin
            // AR: random-accept when no read in flight
            arready <= (!rd_pending && !rvalid) ? lfsr[0] : 1'b0;
            if (arvalid && arready) begin rd_addr_l<=araddr; rd_pending<=1; rd_dly<={1'b0,lfsr[3:1]}; arready<=0; end
            // R: after random delay, present data
            if (rd_pending && !rvalid) begin
                if (rd_dly==0) begin rdata<=mem[rd_addr_l[5 +: BIW]]; rresp<=2'b00; rvalid<=1; rlast<=1; rd_pending<=0; end
                else rd_dly<=rd_dly-1'b1;
            end
            if (rvalid && rready) begin rvalid<=0; rlast<=0; end
            // AW / W: random-accept
            awready <= (!aw_seen) ? lfsr[4] : 1'b0;
            wready  <= (!w_seen)  ? lfsr[5] : 1'b0;
            if (awvalid && awready) begin wr_addr_l<=awaddr; aw_seen<=1; awready<=0; end
            if (wvalid  && wready ) begin wr_data_l<=wdata; w_seen<=1; wready<=0; end
            // commit write when both seen, then random B delay
            if (aw_seen && w_seen && !b_pending && !bvalid) begin
                mem[wr_addr_l[5 +: BIW]]<=wr_data_l; b_pending<=1; b_dly<={1'b0,lfsr[8:6]};
            end
            if (b_pending && !bvalid) begin
                if (b_dly==0) begin bvalid<=1; bresp<=2'b00; b_pending<=0; aw_seen<=0; w_seen<=0; end
                else b_dly<=b_dly-1'b1;
            end
            if (bvalid && bready) bvalid<=0;
        end
    end

    // ---------------- golden model + fragment vectors ----------------
    logic [11:0] vx [0:NFRAG-1]; logic [11:0] vy [0:NFRAG-1]; logic [31:0] vz [0:NFRAG-1]; logic vzmsk [0:NFRAG-1]; logic [1:0] vztst [0:NFRAG-1];
    logic        exp_pass [0:NFRAG-1];
    logic [15:0] gold [0:NPX-1];
    function automatic logic [15:0] clamp16(input logic [31:0] z); clamp16 = (|z[31:16]) ? 16'hFFFF : z[15:0]; endfunction
    function automatic logic zpass(input logic [1:0] op, input logic [15:0] s, input logic [15:0] d);
        case (op) 2'd0:zpass=0; 2'd1:zpass=1; 2'd2:zpass=(s>=d); default:zpass=(s>d); endcase
    endfunction

    // disjoint-range monitor: every Z AXI address must land inside the Z buffer region [ZBASE, ZBASE+NBEATS*32)
    always_ff @(posedge clk) if (rst_n) begin
        if (arvalid && (araddr < ZBASE || araddr >= ZBASE + NBEATS*32)) begin $error("[zrmw] AR addr %h out of Z range", araddr); errors++; end
        if (awvalid && (awaddr < ZBASE || awaddr >= ZBASE + NBEATS*32)) begin $error("[zrmw] AW addr %h out of Z range", awaddr); errors++; end
    end

    int fi_drv, fi_chk;
    initial begin
        errors=0; enable=0; clear_start=0; scene_flush=0; f_valid=0; f_x=0; f_y=0; f_z=0; f_zmsk=0; f_ztst=2'd2; p_ready=0;
        for (int i=0;i<NPX;i++) gold[i]=Z_CLEAR;
        // generate fragments: 3 "epochs" over overlapping regions to force evict->refill persistence + revisits,
        // with same-pixel/same-beat bursts and a fraction of zmsk (test-only) fragments.
        begin int f, reg_w, reg_h, ox, oy; f=0;
            for (int ep=0; ep<3 && f<NFRAG; ep++) begin
                reg_w = 24 + (ep*8); reg_h = 16 + (ep*4); ox = ep*4; oy = ep*2;
                for (int n=0; n<NFRAG/3 && f<NFRAG; n++) begin
                    int lx, ly; logic [31:0] zz; logic zm;
                    lx = ox + ({$random}%reg_w); ly = oy + ({$random}%reg_h);
                    if (lx<0) lx=0; if (lx>=FB_PXW) lx=FB_PXW-1;
                    if (ly<0) ly=0; if (ly>=FB_H) ly=FB_H-1;
                    // Z: mix full-range (clamps) and sub-0xFFFF (discriminating) + occasional same value
                    case ({$random}%4)
                        0: zz = {$random} & 32'h00FF_FFFF;   // 24-bit (often clamps)
                        1: zz = {$random} & 32'h0000_FFFF;   // <=0xFFFF (discriminating)
                        2: zz = {$random} & 32'h0000_3FFF;   // small
                        default: zz = 32'h0001_0000 + ({$random}&32'hFFFF); // just over 0xFFFF -> clamps to 0xFFFF
                    endcase
                    zm = (({$random}%8)==0);                 // ~12% test-only (zmsk=1)
                    vx[f]=lx[11:0]; vy[f]=ly[11:0]; vz[f]=zz; vzmsk[f]=zm;
                    // Preserve the old GEQUAL-heavy stress while explicitly
                    // covering NEVER, ALWAYS and strict GREATER.
                    vztst[f]=(n[3:0]==4'd0)?2'd1:(n[3:0]==4'd1)?2'd0:(n[3:0]==4'd2)?2'd3:2'd2;
                    begin int idx; logic [15:0] sz, dz; logic pass;
                        idx=ly*FB_PXW+lx; sz=clamp16(zz); dz=gold[idx]; pass=zpass(vztst[f],sz,dz);
                        exp_pass[f]=pass; if (pass && !zm) gold[idx]=sz;
                    end
                    f=f+1;
                end
            end
            // pad remainder (if any) with same-pixel bursts to stress forwarding
            while (f<NFRAG) begin
                int idx; logic [15:0] sz, dz; logic pass; logic [31:0] zz;
                vx[f]=12'd10; vy[f]=12'd10; zz={$random}&32'h0000_FFFF; vz[f]=zz; vzmsk[f]=1'b0; vztst[f]=2'd2;
                idx=10*FB_PXW+10; sz=clamp16(zz); dz=gold[idx]; pass=(sz>=dz); exp_pass[f]=pass; if(pass) gold[idx]=sz;
                f=f+1;
            end
        end

        rst_n=0; repeat(6) @(posedge clk); rst_n=1; repeat(4) @(posedge clk);
        enable=1;

        // ---- preclear ----
        @(negedge clk) clear_start=1; @(negedge clk) clear_start=0;
        begin int g=0; while(!clear_done && g<200000) begin @(posedge clk); g++; end end
        if (!clear_done) begin $error("[zrmw] preclear never completed"); errors++; end
        for (int b=0;b<NBEATS;b++) if (mem[b]!=={16{Z_CLEAR}}) begin
            if (errors<8) $error("[zrmw] preclear beat %0d = %h", b, mem[b]); errors++; end
        $display("[zrmw] preclear done: %0d beats = 0x%04x, bresp_err=%0d", NBEATS, Z_CLEAR, bresp_err);

        fork
            begin : DRIVER
                fi_drv=0;
                while (fi_drv<NFRAG) begin
                    // random bubbles on the producer side
                    if (lfsr[9]) begin f_valid<=0; @(posedge clk); end
                    else begin
                        f_valid<=1; f_x<=vx[fi_drv]; f_y<=vy[fi_drv]; f_z<=vz[fi_drv]; f_zmsk<=vzmsk[fi_drv]; f_ztst<=vztst[fi_drv];
                        @(posedge clk);
                        if (f_valid && f_ready) fi_drv=fi_drv+1;
                    end
                end
                f_valid<=0;
            end
            begin : CHECKER
                fi_chk=0;
                while (fi_chk<NFRAG) begin
                    p_ready <= lfsr[11];                     // random consumer backpressure
                    @(posedge clk);
                    if (p_valid && p_ready) begin
                        if (p_pass !== exp_pass[fi_chk]) begin
                            if (errors<16) $error("[zrmw] frag %0d (%0d,%0d z=%h zmsk=%b ztst=%0d): pass=%b exp=%b zq=%h",
                                fi_chk, p_x, p_y, vz[fi_chk], vzmsk[fi_chk], vztst[fi_chk], p_pass, exp_pass[fi_chk], p_zq); errors++; end
                        if (p_x!==vx[fi_chk] || p_y!==vy[fi_chk]) begin
                            if (errors<16) $error("[zrmw] frag %0d result xy (%0d,%0d) exp (%0d,%0d)", fi_chk,p_x,p_y,vx[fi_chk],vy[fi_chk]); errors++; end
                        fi_chk=fi_chk+1;
                    end
                end
                p_ready<=1;
            end
        join

        // ---- scene-end flush: assert scene_flush, wait z_drained; then ALL stored Z (in LPDDR) must equal golden ----
        // (this also proves the cache PERSISTS: after the flush it stays valid+clean, and mem is fully durable.)
        repeat(20) @(posedge clk);
        scene_flush<=1'b1;
        begin int g=0; while(!z_drained && g<200000) begin @(posedge clk); g++; end end
        if (!z_drained) begin $error("[zrmw] scene_flush: z_drained never asserted"); errors++; end
        if (dut.cache_dirty) begin $error("[zrmw] scene_flush: cache still dirty after drain"); errors++; end
        if (!dut.cache_valid) begin $error("[zrmw] scene_flush: cache invalidated (Z must persist across epochs)"); errors++; end
        repeat(4) @(posedge clk); scene_flush<=1'b0;
        for (int idx=0; idx<NPX; idx++) begin
            int b, ln; logic [15:0] stored;
            b=idx>>4; ln=idx[3:0]; stored=mem[b][ln*16 +: 16];   // no cache peek: after scene flush, LPDDR is authoritative
            if (stored!==gold[idx]) begin
                if (errors<16) $error("[zrmw] durable Z px %0d (beat %0d lane %0d) = %04x exp %04x", idx,b,ln,stored,gold[idx]); errors++; end
        end
        $display("[zrmw] scene_flush drained: z_drained=%0b cache_valid=%0b(persist) cache_dirty=%0b; LPDDR==golden",
                 z_drained, dut.cache_valid, dut.cache_dirty);

        if (bresp_err!==0) begin $error("[zrmw] bresp_err=%0d", bresp_err); errors++; end
        if (fi_drv!==NFRAG || fi_chk!==NFRAG) begin $error("[zrmw] DROP: driven=%0d checked=%0d exp %0d", fi_drv, fi_chk, NFRAG); errors++; end
        $display("[zrmw] frags driven=%0d checked=%0d (zero drops) beats_read=%0d beats_written=%0d bresp_err=%0d errors=%0d",
                 fi_drv, fi_chk, beats_read, beats_written, bresp_err, errors);
        if (errors==0) $display("[tb_gs_lpddr_z_rmw] PASS"); else $display("[tb_gs_lpddr_z_rmw] FAIL");
        $finish;
    end
    initial begin #20000000; $error("[tb_gs_lpddr_z_rmw] TIMEOUT"); $finish; end
endmodule : tb_gs_lpddr_z_rmw
