// retroDE_ps2 — tb_gs_lpddr_zc_emit  (Ch357 — Z-then-color integration unit TB)
//
// Two-clock (gs_clk != axi_clk) test of the integration wrapper against a software golden model. Covers Codex gate-7:
//   * request-FIFO backpressure (g_ready) across the CDC.
//   * REJECTED-COLOR SUPPRESSION: a fragment failing Z writes NO color.
//   * ordered SCENE-MARKER drain: after each marker, combined frame_drained rises (color + Z BRESPs complete).
//   * Z cache PERSISTENCE across epochs: Z precleared ONCE at frame start; 3 epochs accumulate.
//   * DISJOINT ranges: Z AXI in [ZBASE..), color AXI in [COLBASE..); the two never cross.
//   * col_ovf==0, bresp_err==0; final color FB == golden (Z-winning color per pixel); final Z == golden.
`timescale 1ns/1ps

module tb_gs_lpddr_zc_emit;
    localparam int FB_PXW=32, FB_H=16;
    localparam int NPX=FB_PXW*FB_H;               // 512
    localparam int ZBEATS=(NPX+15)/16;            // 32
    localparam int CBEATS=(NPX+7)/8;              // 64  (8 PSMCT32 / beat)
    localparam [31:0] COLBASE=32'h0000_0000, ZBASE=32'h0014_0000;
    localparam int NFRAG=1500, NEP=3;
    localparam int ZBW=$clog2(ZBEATS), CBW=$clog2(CBEATS);

    logic gs_clk=0;  always #5 gs_clk=~gs_clk;
    logic axi_clk=0; always #7 axi_clk=~axi_clk;
    logic gs_rst_n, axi_rst_n; int errors; initial errors=0;

    logic enable, clear_start, clear_done, frame_drained;
    logic g_valid, g_ready; logic [11:0] g_x, g_y; logic [15:0] g_zq; logic g_zmsk, g_ztest, g_scene; logic [1:0] g_ztst; logic [31:0] g_color;
    // Z AXI
    logic [31:0] z_araddr; logic [7:0] z_arlen; logic [2:0] z_arsize; logic [1:0] z_arburst; logic z_arvalid, z_arready;
    logic [255:0] z_rdata; logic [1:0] z_rresp; logic z_rlast, z_rvalid, z_rready;
    logic [31:0] z_awaddr; logic [7:0] z_awlen; logic [2:0] z_awsize; logic [1:0] z_awburst; logic z_awvalid, z_awready;
    logic [255:0] z_wdata; logic [31:0] z_wstrb; logic z_wlast, z_wvalid, z_wready; logic z_bvalid, z_bready; logic [1:0] z_bresp;
    // Color AXI
    logic [31:0] c_awaddr; logic [7:0] c_awlen; logic [2:0] c_awsize; logic [1:0] c_awburst; logic c_awvalid, c_awready;
    logic [255:0] c_wdata; logic [31:0] c_wstrb; logic c_wlast, c_wvalid, c_wready; logic c_bvalid, c_bready; logic [1:0] c_bresp;
    logic [31:0] z_beats_read, z_beats_written, c_beats_written, col_ovf, bresp_err; logic idle;

    gs_lpddr_zc_emit #(.COLBASE(COLBASE), .ZBASE(ZBASE), .FB_PXW(FB_PXW), .FB_H(FB_H), .REQ_DEPTH(16), .COL_DEPTH(64)) dut (
        .gs_clk(gs_clk), .gs_rst_n(gs_rst_n), .enable(enable),
        .g_valid(g_valid), .g_ready(g_ready), .g_x(g_x), .g_y(g_y), .g_zq(g_zq), .g_zmsk(g_zmsk),
        .g_ztest(g_ztest), .g_ztst(g_ztst), .g_color(g_color), .g_be(4'hF), .g_scene(g_scene),
        .axi_clk(axi_clk), .axi_rst_n(axi_rst_n), .clear_start(clear_start), .clear_done(clear_done), .frame_drained(frame_drained),
        .z_araddr(z_araddr), .z_arlen(z_arlen), .z_arsize(z_arsize), .z_arburst(z_arburst), .z_arvalid(z_arvalid), .z_arready(z_arready),
        .z_rdata(z_rdata), .z_rresp(z_rresp), .z_rlast(z_rlast), .z_rvalid(z_rvalid), .z_rready(z_rready),
        .z_awaddr(z_awaddr), .z_awlen(z_awlen), .z_awsize(z_awsize), .z_awburst(z_awburst), .z_awvalid(z_awvalid), .z_awready(z_awready),
        .z_wdata(z_wdata), .z_wstrb(z_wstrb), .z_wlast(z_wlast), .z_wvalid(z_wvalid), .z_wready(z_wready),
        .z_bvalid(z_bvalid), .z_bready(z_bready), .z_bresp(z_bresp),
        .c_awaddr(c_awaddr), .c_awlen(c_awlen), .c_awsize(c_awsize), .c_awburst(c_awburst), .c_awvalid(c_awvalid), .c_awready(c_awready),
        .c_wdata(c_wdata), .c_wstrb(c_wstrb), .c_wlast(c_wlast), .c_wvalid(c_wvalid), .c_wready(c_wready),
        .c_bvalid(c_bvalid), .c_bready(c_bready), .c_bresp(c_bresp),
        .z_beats_read(z_beats_read), .z_beats_written(z_beats_written), .c_beats_written(c_beats_written),
        .col_ovf(col_ovf), .bresp_err(bresp_err), .idle(idle)
    );

    logic [15:0] lf=16'hBEEF; always_ff @(posedge axi_clk) lf<={lf[14:0], lf[15]^lf[13]^lf[12]^lf[10]};

    // ---- Z LPDDR slave (256b, random backpressure) ----
    logic [255:0] zmem [0:ZBEATS-1];
    logic [31:0] zra; logic zrp; logic [3:0] zrd; logic [31:0] zwa; logic [255:0] zwd; logic zaw,zw; logic [3:0] zbd; logic zbp;
    always_ff @(posedge axi_clk or negedge axi_rst_n) begin
        if(!axi_rst_n) begin z_arready<=0;z_rvalid<=0;z_rlast<=0;z_rresp<=0;z_rdata<=0;zrp<=0;zrd<=0;
                             z_awready<=0;z_wready<=0;z_bvalid<=0;z_bresp<=0;zaw<=0;zw<=0;zbp<=0;zbd<=0; end
        else begin
            z_arready<=(!zrp&&!z_rvalid)?lf[0]:1'b0;
            if(z_arvalid&&z_arready) begin zra<=z_araddr;zrp<=1;zrd<={1'b0,lf[3:1]};z_arready<=0; end
            if(zrp&&!z_rvalid) begin if(zrd==0) begin z_rdata<=zmem[zra[5+:ZBW]];z_rresp<=0;z_rvalid<=1;z_rlast<=1;zrp<=0; end else zrd<=zrd-1; end
            if(z_rvalid&&z_rready) begin z_rvalid<=0;z_rlast<=0; end
            z_awready<=(!zaw)?lf[4]:1'b0; z_wready<=(!zw)?lf[5]:1'b0;
            if(z_awvalid&&z_awready) begin zwa<=z_awaddr;zaw<=1;z_awready<=0; end
            if(z_wvalid&&z_wready) begin zwd<=z_wdata;zw<=1;z_wready<=0; end
            if(zaw&&zw&&!zbp&&!z_bvalid) begin zmem[zwa[5+:ZBW]]<=zwd;zbp<=1;zbd<={1'b0,lf[8:6]}; end
            if(zbp&&!z_bvalid) begin if(zbd==0) begin z_bvalid<=1;z_bresp<=0;zbp<=0;zaw<=0;zw<=0; end else zbd<=zbd-1; end
            if(z_bvalid&&z_bready) z_bvalid<=0;
        end
    end

    // ---- Color LPDDR slave (256b, per-byte wstrb, random backpressure) ----
    logic [255:0] cmem [0:CBEATS-1];
    logic [31:0] cwa; logic [255:0] cwd; logic [31:0] cws; logic caw,cw; logic [3:0] cbd; logic cbp;
    logic col_force_stall = 1'b0;   // Ch357 (Codex) — PROLONGED px_ready=0: hard-stall the color AXI to fully back up
                                    // u_c's FIFO -> col_px_ready drops -> the new zc_emit output register must HOLD.
    always_ff @(posedge axi_clk or negedge axi_rst_n) begin
        if(!axi_rst_n) begin c_awready<=0;c_wready<=0;c_bvalid<=0;c_bresp<=0;caw<=0;cw<=0;cbp<=0;cbd<=0; end
        else begin
            c_awready<=(!caw && !col_force_stall)?lf[9]:1'b0; c_wready<=(!cw && !col_force_stall)?lf[11]:1'b0;
            if(c_awvalid&&c_awready) begin cwa<=c_awaddr;caw<=1;c_awready<=0; end
            if(c_wvalid&&c_wready) begin cwd<=c_wdata;cws<=c_wstrb;cw<=1;c_wready<=0; end
            if(caw&&cw&&!cbp&&!c_bvalid) begin
                for(int b=0;b<32;b++) if(cws[b]) cmem[cwa[5+:CBW]][b*8+:8]<=cwd[b*8+:8];
                cbp<=1;cbd<={1'b0,lf[14:12]};
            end
            if(cbp&&!c_bvalid) begin if(cbd==0) begin c_bvalid<=1;c_bresp<=0;cbp<=0;caw<=0;cw<=0; end else cbd<=cbd-1; end
            if(c_bvalid&&c_bready) c_bvalid<=0;
        end
    end

    // ---- disjoint-range monitors ----
    always_ff @(posedge axi_clk) if(axi_rst_n) begin
        if(z_arvalid && (z_araddr<ZBASE || z_araddr>=ZBASE+ZBEATS*32)) begin $error("Z AR %h out of range",z_araddr);errors++; end
        if(z_awvalid && (z_awaddr<ZBASE || z_awaddr>=ZBASE+ZBEATS*32)) begin $error("Z AW %h out of range",z_awaddr);errors++; end
        if(c_awvalid && (c_awaddr<COLBASE || c_awaddr>=COLBASE+CBEATS*32)) begin $error("C AW %h out of range",c_awaddr);errors++; end
    end

    // ---- golden ----
    logic [15:0] zg [0:NPX-1]; logic [31:0] cg [0:NPX-1]; logic cw_seen [0:NPX-1];
    logic [11:0] vx[0:NFRAG-1],vy[0:NFRAG-1]; logic [15:0] vz[0:NFRAG-1]; logic vzm[0:NFRAG-1]; logic [31:0] vc[0:NFRAG-1];
    int fdrv;
    function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction

    // simple producer: drive one item, wait accept
    task automatic push(input logic sc, input logic [11:0] x, input logic [11:0] y, input logic [15:0] zq,
                        input logic zm, input logic [1:0] zt, input logic [31:0] col);
        @(negedge gs_clk);
        g_valid<=1; g_scene<=sc; g_x<=x; g_y<=y; g_zq<=zq; g_zmsk<=zm; g_ztest<=1'b1; g_ztst<=zt; g_color<=col;
        @(posedge gs_clk);
        while(!(g_valid && g_ready)) @(posedge gs_clk);
        @(negedge gs_clk); g_valid<=0;
        // random gap
        if(lf[7]) begin repeat(1+(lf[2:0])) @(posedge gs_clk); end
    endtask

    initial begin
        errors=0; enable=0; clear_start=0; g_valid=0; g_scene=0; g_x=0; g_y=0; g_zq=0; g_zmsk=0; g_ztest=1; g_ztst=2'd2; g_color=0;
        for(int i=0;i<NPX;i++) begin zg[i]=16'h0000; cg[i]=32'd0; cw_seen[i]=1'b0; end
        for(int b=0;b<CBEATS;b++) cmem[b]=256'd0;    // host preclears the color FB to 0 (so never-won pixels stay 0)
        // precompute fragment vectors + golden (3 epochs over overlapping regions; Z spread; ~10% zmsk)
        begin int f; f=0;
            for(int ep=0;ep<NEP && f<NFRAG;ep++) begin
                int rw,rh,ox,oy; rw=16+ep*4; rh=10+ep*2; ox=ep*3; oy=ep*1;
                for(int n=0;n<NFRAG/NEP && f<NFRAG;n++) begin
                    int lx,ly; logic [31:0] zz; logic zm; logic [31:0] col;
                    lx=ox+({$random}%rw); ly=oy+({$random}%rh);
                    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;
                    case({$random}%3) 0: zz={$random}&32'h0000_FFFF; 1: zz={$random}&32'h0000_3FFF; default: zz={$random}&32'h00FF_FFFF; endcase
                    zm=(({$random}%10)==0); col={$random}|32'h0000_0001;   // nonzero color to distinguish "written"
                    vx[f]=lx[11:0];vy[f]=ly[11:0];vz[f]=cl16(zz); vzm[f]=zm; vc[f]=col;
                    begin int idx; logic [15:0] sz; logic pass; idx=ly*FB_PXW+lx; sz=cl16(zz); pass=(sz>=zg[idx]);
                        if(pass) begin cg[idx]=col; cw_seen[idx]=1'b1; if(!zm) zg[idx]=sz; end
                    end
                    f=f+1;
                end
            end
        end

        gs_rst_n=0; axi_rst_n=0; repeat(6) @(posedge axi_clk); gs_rst_n=1; axi_rst_n=1; repeat(4) @(posedge axi_clk);
        enable=1;
        // preclear Z once at frame start
        @(negedge axi_clk) clear_start=1; @(negedge axi_clk) clear_start=0;
        begin int g=0; while(!clear_done && g<200000) begin @(posedge axi_clk); g++; end end
        if(!clear_done) begin $error("[zc] preclear timeout");errors++; end

        // drive 3 epochs, marker + wait frame_drained after each
        fdrv=0;
        // Ch357 (Codex) — PROLONGED px_ready=0 coverage: once a handful of fragments are in flight, hard-stall the color
        // AXI for a long window so u_c's FIFO fully backs up and col_px_ready stays 0; the zc_emit output register must
        // HOLD its pixel (no drop). Release and let it drain. The exact-framebuffer + col_ovf==0 checks below prove it.
        fork
            begin
                while (fdrv < 12) @(posedge axi_clk);
                col_force_stall = 1'b1; repeat(500) @(posedge axi_clk); col_force_stall = 1'b0;
            end
        join_none
        for(int ep=0;ep<NEP;ep++) begin
            int lo,hi; lo=ep*(NFRAG/NEP); hi=(ep+1)*(NFRAG/NEP);
            for(int f=lo;f<hi;f++) begin push(1'b0, vx[f],vy[f],vz[f],vzm[f],2'd2,vc[f]); fdrv++; end
            // end-of-scene marker
            push(1'b1, 12'd0,12'd0,16'd0,1'b0,2'd2,32'd0);
            begin int g=0; while(!frame_drained && g<400000) begin @(posedge axi_clk); g++; end end
            if(!frame_drained) begin $error("[zc] epoch %0d: frame_drained never rose",ep);errors++; end
            $display("[zc] epoch %0d drained (frame_drained=1) fdrv=%0d z_wr=%0d c_wr=%0d col_ovf=%0d",ep,fdrv,z_beats_written,c_beats_written,col_ovf);
            repeat(30) @(posedge axi_clk);
        end

        // Fidelity gate: the SH3 darken sprites use TEST.ZTST=ALWAYS with a
        // low incoming Z.  Prove that mode survives the gs->axi FIFO instead
        // of being collapsed to GEQUAL.  ZMSK preserves the existing depth;
        // the unmistakable color must nevertheless commit.
        begin
            int idx, g;
            logic [15:0] z_before;
            idx = 7*FB_PXW + 11;
            z_before = zg[idx];
            push(1'b0, 12'd11, 12'd7, 16'h0000, 1'b1, 2'd1, 32'h50A5_3CC3);
            cg[idx] = 32'h50A5_3CC3; cw_seen[idx] = 1'b1;
            g=0; while(frame_drained && g<2000) begin @(posedge axi_clk); g++; end
            push(1'b1, 12'd0,12'd0,16'd0,1'b0,2'd2,32'd0);
            g=0; while(!frame_drained && g<400000) begin @(posedge axi_clk); g++; end
            if(!frame_drained) begin $error("[zc] ZTST=ALWAYS directed epoch never drained"); errors++; end
            if(zg[idx] !== z_before) begin $error("[zc] directed ZMSK golden changed unexpectedly"); errors++; end
            $display("[zc] directed ZTST=ALWAYS accepted low-Z color at (%0d,%0d), preserved Z=%04x",11,7,z_before);
        end

        // ---- final checks: Z LPDDR == golden ; color LPDDR == golden (written pixels) ----
        repeat(40) @(posedge axi_clk);
        for(int idx=0;idx<NPX;idx++) begin
            logic [15:0] zs; zs=zmem[idx>>4][(idx[3:0])*16+:16];
            if(zs!==zg[idx]) begin if(errors<12)$error("[zc] Z px%0d=%04x exp %04x",idx,zs,zg[idx]);errors++; end
        end
        for(int idx=0;idx<NPX;idx++) if(cw_seen[idx]) begin
            logic [31:0] cs; cs=cmem[idx>>3][(idx[2:0])*32+:32];
            if(cs!==cg[idx]) begin if(errors<12)$error("[zc] COLOR px%0d=%08x exp %08x",idx,cs,cg[idx]);errors++; end
        end
        // pixels never won by any fragment must have NO color written (still 0) — rejected-color suppression sanity
        for(int idx=0;idx<NPX;idx++) if(!cw_seen[idx]) begin
            logic [31:0] cs; cs=cmem[idx>>3][(idx[2:0])*32+:32];
            if(cs!==32'd0) begin if(errors<12)$error("[zc] px%0d never-won but color=%08x (suppression fail)",idx,cs);errors++; end
        end

        if(col_ovf!==0) begin $error("[zc] col_ovf=%0d",col_ovf);errors++; end
        if(bresp_err!==0) begin $error("[zc] bresp_err=%0d",bresp_err);errors++; end
        if(fdrv!==NFRAG) begin $error("[zc] fdrv=%0d exp %0d",fdrv,NFRAG);errors++; end
        $display("[zc] frags=%0d z_read=%0d z_wr=%0d c_wr=%0d col_ovf=%0d bresp_err=%0d errors=%0d",
                 fdrv,z_beats_read,z_beats_written,c_beats_written,col_ovf,bresp_err,errors);
        if(errors==0) $display("[tb_gs_lpddr_zc_emit] PASS"); else $display("[tb_gs_lpddr_zc_emit] FAIL");
        $finish;
    end
    initial begin #60000000; $error("[tb_gs_lpddr_zc_emit] TIMEOUT"); $finish; end
endmodule : tb_gs_lpddr_zc_emit
