// retroDE_ps2 — tb_top_psmct32_sh3_zrop  (Ch357 — persistent-Z ROP acceptance gate, TRACE-based scoreboard)
//
// Codex option A: verify the LPDDR-Z ROP (gs_lpddr_zc_emit) against the raster's ACTUAL emitted fragments. This TB replays
// the captured fragment trace (tb_top_psmct32_sh3_zsched -> zsched_frags.txt: "epoch x y persp_z color" per line) through
// zc_emit across two clock domains, with a per-epoch scene marker, and scores it with an INDEPENDENT software scoreboard
// (clamp16 / GEQUAL / persistence). Proves Codex's Ch357 gates:
//   * every replayed fragment enters the ROP EXACTLY once (we feed each once and wait g_ready — no drop, no double).
//   * the scoreboard predicts every pass/reject and the final packed 16-bit Z; failed depth tests emit NO color.
//   * final color + Z LPDDR memories match the scoreboard EXACTLY.
//   * the ordered end-of-scene marker drain waits BOTH pipelines (color + Z BRESPs) before frame_drained.
// This is the ROP-correctness gate; the raster's Z-interp fidelity (float-oracle 3971 / 87.6% owner agreement) is a SEPARATE
// recorded limitation, not tested here. Primary fixture = sh3_zsched [8634,12757,145742], FB 256x210, Z base 0x140000.
`timescale 1ns/1ps

module tb_top_psmct32_sh3_zrop;
    localparam int FB_PXW=256, FB_H=210;
    localparam int NPX=FB_PXW*FB_H;               // 53760
    localparam int ZBEATS=(NPX+15)/16;            // 3360
    localparam int CBEATS=(NPX+7)/8;              // 6720
    localparam [31:0] COLBASE=32'h0000_0000, ZBASE=32'h0014_0000;
    localparam int ZBW=$clog2(ZBEATS), CBW=$clog2(CBEATS);

    logic gs_clk=0;  always #5 gs_clk=~gs_clk;
    logic axi_clk=0; always #2 axi_clk=~axi_clk;   // emif faster than design -> ROP drains faster than the trace feeds
    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 [31:0] g_color;
    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;
    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(32), .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(2'd2), .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'hCAFE; 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-ZBASE)>>5];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-ZBASE)>>5]<=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;
    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)?lf[9]:1'b0; c_wready<=(!cw)?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-COLBASE)>>5][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 OOR",z_araddr);errors++; end
        if(z_awvalid && (z_awaddr<ZBASE || z_awaddr>=ZBASE+ZBEATS*32)) begin $error("Z AW %h OOR",z_awaddr);errors++; end
        if(c_awvalid && (c_awaddr<COLBASE || c_awaddr>=COLBASE+CBEATS*32)) begin $error("C AW %h OOR",c_awaddr);errors++; end
    end

    // ---- independent scoreboard (clamp16 persistent-Z over the fed fragments) ----
    logic [15:0] sb_z [0:NPX-1]; logic [31:0] sb_col [0:NPX-1]; logic sb_seen [0:NPX-1];
    function automatic logic [15:0] cl16(input logic [31:0] z); cl16=(|z[31:16])?16'hFFFF:z[15:0]; endfunction

    // ---- feed one fragment: drive g_*, wait acceptance (exactly once), update the scoreboard ----
    int nfed;
    task automatic feed_frag(input int ep, input int x, input int y, input int z, input logic [31:0] col);
        int o; logic [15:0] zq; logic pass;
        @(negedge gs_clk);
        g_valid<=1; g_scene<=0; g_x<=x[11:0]; g_y<=y[11:0]; g_zq<=cl16(z); g_zmsk<=0; g_ztest<=1; g_color<=col;
        @(posedge gs_clk);
        while(!(g_valid && g_ready)) @(posedge gs_clk);       // wait acceptance — NEVER drop a fragment
        @(negedge gs_clk); g_valid<=0;
        // scoreboard (in feed order == ROP processing order)
        o=y*FB_PXW+x; zq=cl16(z); pass=(zq>=sb_z[o]);
        if(pass) begin sb_col[o]=col; sb_seen[o]=1; sb_z[o]=zq; end
        nfed++;
        if(lf[7]) repeat(1+lf[1:0]) @(posedge gs_clk);        // random inter-fragment gap
    endtask
    task automatic send_marker();
        @(negedge gs_clk); g_valid<=1; g_scene<=1; @(posedge gs_clk);
        while(!(g_valid && g_ready)) @(posedge gs_clk);
        @(negedge gs_clk); g_valid<=0; g_scene<=0;
    endtask

    int fh, r, ep, x, y, z, cep, drains; logic [31:0] col; logic [1023:0] line;
    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_color=0; nfed=0; drains=0;
        for(int i=0;i<NPX;i++) begin sb_z[i]=16'h0000; sb_col[i]=32'd0; sb_seen[i]=1'b0; end
        for(int b=0;b<CBEATS;b++) cmem[b]=256'd0;                 // host preclears the color FB
        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;
        @(negedge axi_clk) clear_start=1; @(negedge axi_clk) clear_start=0;   // preclear Z once
        begin int g=0; while(!clear_done && g<400000) begin @(posedge axi_clk); g++; end end
        if(!clear_done) begin $error("[zrop] Z preclear timeout"); errors++; end

        fh=$fopen("zsched_frags.txt","r");
        if(fh==0) begin $display("[tb_top_psmct32_sh3_zrop] SKIP — zsched_frags.txt absent (run make tb_top_psmct32_sh3_zsched)"); $finish; end
        cep=0;
        while(!$feof(fh)) begin
            // zsched_frags.txt currently carries "epoch x y z color u v". Read one full line so the optional
            // trailing texture-debug fields cannot be re-parsed as a bogus next fragment.
            if(!$fgets(line, fh)) begin end
            r=$sscanf(line, "%d %d %d %d %h", ep, x, y, z, col);
            if(r==5) begin
                if(x<0 || x>=FB_PXW || y<0 || y>=FB_H) begin
                    $error("[zrop] trace coord OOR ep=%0d x=%0d y=%0d z=%0d col=%08x", ep, x, y, z, col);
                    errors++;
                end else begin
                    if(ep!=cep) begin                                // epoch boundary -> ordered scene drain
                        send_marker();
                        begin int g=0; while(!frame_drained && g<800000) begin @(posedge axi_clk); g++; end end
                        if(!frame_drained) begin $error("[zrop] epoch %0d: frame_drained never rose",cep); errors++; end
                        else drains++;
                        cep=ep;
                    end
                    feed_frag(ep, x, y, z, col);
                end
            end
        end
        $fclose(fh);
        // final scene marker (last epoch)
        send_marker();
        begin int g=0; while(!frame_drained && g<800000) begin @(posedge axi_clk); g++; end end
        if(!frame_drained) begin $error("[zrop] final: frame_drained never rose"); errors++; end else drains++;
        repeat(60) @(posedge axi_clk);

        // ---- gates: final Z + color LPDDR == scoreboard ; suppression ; counts ----
        for(int o=0;o<NPX;o++) begin
            logic [15:0] zs; zs=zmem[o>>4][(o[3:0])*16+:16];
            if(zs!==sb_z[o]) begin if(errors<12)$error("[zrop] Z px%0d=%04x exp %04x",o,zs,sb_z[o]);errors++; end
        end
        for(int o=0;o<NPX;o++) begin
            logic [31:0] cs; cs=cmem[o>>3][(o[2:0])*32+:32];
            if(sb_seen[o]) begin if(cs!==sb_col[o]) begin if(errors<12)$error("[zrop] COLOR px%0d=%08x exp %08x",o,cs,sb_col[o]);errors++; end end
            else            begin if(cs!==32'd0)    begin if(errors<12)$error("[zrop] px%0d never-passed but color=%08x (suppression fail)",o,cs);errors++; end end
        end
        if(col_ovf!==0)  begin $error("[zrop] col_ovf=%0d",col_ovf);errors++; end
        if(bresp_err!==0)begin $error("[zrop] bresp_err=%0d",bresp_err);errors++; end
        $display("[zrop] fed=%0d fragments, scene-drains=%0d, z_wr=%0d c_wr=%0d col_ovf=%0d bresp_err=%0d errors=%0d",
                 nfed, drains, z_beats_written, c_beats_written, col_ovf, bresp_err, errors);
        if(errors==0) $display("[tb_top_psmct32_sh3_zrop] PASS"); else $display("[tb_top_psmct32_sh3_zrop] FAIL");
        $finish;
    end
    initial begin #400000000; $error("[tb_top_psmct32_sh3_zrop] TIMEOUT"); $finish; end
endmodule : tb_top_psmct32_sh3_zrop
