// retroDE_ps2 — tb_gs_tile_spill_lpddr  (Ch323 Brick 2 — REAL LPDDR-path integration proof)
//
// The gap that let the board bugs through: tb_gs_tile_spill_reload (Brick 1) forced
// tile_reload_ready=1 and served an on-chip backing — it NEVER exercised the real
// gs_z_flush_writer / gs_tile_reload / EMIF round-trip, and gs_stub PULSES reload_start
// while gs_tile_reload edge-detects it (pulse-vs-toggle). This TB wires the SAME modules
// the de25 wires — gs_stub -> 2x gs_z_flush_writer (color/Z) -> behavioral 256-bit EMIF
// -> gs_tile_reload -> gs_stub serve port — across an ASYNC emif clock, and proves the
// two-batch depth result (region A = red color1, region B = blue color2) THROUGH THE REAL
// DATA PATH. Small LPDDR bases (C=0, Z=0x1000) keep the behavioral mem tiny.
`timescale 1ns/1ps

module tb_gs_tile_spill_lpddr_neg;
    localparam int H_ACTIVE = 16, V_ACTIVE = 16;
    localparam logic [31:0] COLOR1 = 32'hFF0000FF, COLOR2 = 32'hFFFF0000;  // red / blue (ABGR)
    localparam [29:0] C_BASE = 30'h0000_0000, Z_BASE = 30'h0000_1000;

    logic clk=0;  always #5 clk  = ~clk;    // design clock
    logic eclk=0; always #3 eclk = ~eclk;   // emif clock (faster, async)
    logic rst_n, erst_n, core_go;

    logic [7:0] r,g,b; logic hsync,vsync,de;
    logic core_halt, dma_done_seen, frame_seen, raster_overflow, frame_toggle, dma_done_toggle;
    logic        z_flush_emit; logic [31:0] z_flush_addr, z_flush_data;
    logic        flush_emit;   logic [31:0] flush_addr, flush_color32;
    logic        cflush_emit;  logic [31:0] cflush_addr, cflush_data;
    logic        reload_start;  logic [7:0] tile_reload_raddr; logic [29:0] reload_base;
    logic [31:0] tile_reload_color, tile_reload_z; logic tile_reload_ready;
    logic [2:0]  tile_phase_o;

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE(H_ACTIVE), .V_ACTIVE(V_ACTIVE),
        .VRAM_BYTES(8*1024), .PSMCT32_SWIZZLE(1'b0),
        .COMBINED_TAZ(1'b1), .TILE_LOCAL(1'b1), .TILE_SPILL_ENABLE(1'b1), .SPILL_FORCE_VALID(1'b1)
    ) dut (
        .clk(clk), .rst_n(rst_n), .core_go(core_go),
        .r(r), .g(g), .b(b), .hsync(hsync), .vsync(vsync), .de(de),
        .core_halt(core_halt), .dma_done_seen(dma_done_seen),
        .frame_seen(frame_seen), .raster_overflow(raster_overflow),
        .frame_toggle(frame_toggle), .dma_done_toggle(dma_done_toggle),
        .joy_a_pressed_i(1'b0), .joy_b_pressed_i(1'b0),
        .flush_emit_o(flush_emit), .flush_addr_o(flush_addr), .flush_color32_o(flush_color32),
        .tile_color_flush_emit_o(cflush_emit), .tile_color_flush_addr_o(cflush_addr), .tile_color_flush_data_o(cflush_data),
        .z_flush_emit_o(z_flush_emit), .z_flush_addr_o(z_flush_addr), .z_flush_data_o(z_flush_data),
        .reload_start_o(reload_start), .tile_reload_raddr_o(tile_reload_raddr), .reload_base_o(reload_base),
        .tile_reload_ready_i(tile_reload_ready), .tile_reload_color_i(tile_reload_color),
        .tile_reload_z_i(tile_reload_z), .tile_phase_o(tile_phase_o)
    );

    // ================= REAL spill writers (color + Z) =================
    wire [29:0] cw_aa, zw_aa; wire [1:0] cw_ab, zw_ab; wire [6:0] cw_id, zw_id;
    wire [7:0] cw_al, zw_al; wire [2:0] cw_as, zw_as; wire cw_av, zw_av, cw_ar, zw_ar;
    wire [255:0] cw_wd, zw_wd; wire [31:0] cw_ws, zw_ws; wire cw_wl, zw_wl, cw_wv, zw_wv, cw_wr, zw_wr;
    wire [1:0] cw_br, zw_br; wire cw_bv, zw_bv, cw_brd, zw_brd;
    wire [31:0] c_beats, c_errs, z_beats, z_errs; wire c_ovf, z_ovf;

    gs_z_flush_writer #(.Z_BASE(C_BASE), .FB_BASE(30'h8000), .FB_BYTES(32'h1000),
                        .TEX_BASE(30'h9000), .TEX_BYTES(32'h1000)) u_color (
        .gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1), .trace_clear(1'b0),
        .z_flush_emit(cflush_emit), .z_flush_addr(cflush_addr), .z_flush_data(cflush_data),
        .z_write_beats(c_beats), .z_wr_errs(c_errs), .fifo_overflow(c_ovf),
        .axi_clk(eclk), .axi_rst_n(erst_n),
        .awaddr(cw_aa), .awburst(cw_ab), .awid(cw_id), .awlen(cw_al), .awsize(cw_as),
        .awvalid(cw_av), .awready(cw_ar), .wdata(cw_wd), .wstrb(cw_ws), .wlast(cw_wl),
        .wvalid(cw_wv), .wready(cw_wr), .bresp(cw_br), .bvalid(cw_bv), .bready(cw_brd)
    );
    gs_z_flush_writer #(.Z_BASE(Z_BASE), .FB_BASE(30'h8000), .FB_BYTES(32'h1000),
                        .TEX_BASE(30'h9000), .TEX_BYTES(32'h1000)) u_z (
        .gs_clk(clk), .gs_rst_n(rst_n), .enable(1'b1), .trace_clear(1'b0),
        .z_flush_emit(z_flush_emit), .z_flush_addr(z_flush_addr), .z_flush_data(z_flush_data),
        .z_write_beats(z_beats), .z_wr_errs(z_errs), .fifo_overflow(z_ovf),
        .axi_clk(eclk), .axi_rst_n(erst_n),
        .awaddr(zw_aa), .awburst(zw_ab), .awid(zw_id), .awlen(zw_al), .awsize(zw_as),
        .awvalid(zw_av), .awready(zw_ar), .wdata(zw_wd), .wstrb(zw_ws), .wlast(zw_wl),
        .wvalid(zw_wv), .wready(zw_wr), .bresp(zw_br), .bvalid(zw_bv), .bready(zw_brd)
    );

    // ================= REAL reload engine =================
    wire [29:0] rl_aa; wire [1:0] rl_ab; wire [6:0] rl_id; wire [7:0] rl_al; wire [2:0] rl_as;
    wire rl_av, rl_ar; wire [255:0] rl_rd; wire [1:0] rl_rr; wire rl_rl, rl_rv, rl_rrdy;
    wire [31:0] rl_cbeats, rl_zbeats, rl_rderrs;
    gs_tile_reload #(.COLOR_BASE(C_BASE), .Z_BASE(Z_BASE), .TILE_W(16), .TILE_H(16),
                     .STRIDE_BYTES(256), .ROW_BEATS(2), .COLOR_W(32)) u_reload (
        .axi_clk(eclk), .axi_rst_n(erst_n),
        .reload_start(reload_start), .reload_base(reload_base), .reload_done(),
        .color_beats(rl_cbeats), .z_beats(rl_zbeats), .rd_errs(rl_rderrs),
        .araddr(rl_aa), .arburst(rl_ab), .arid(rl_id), .arlen(rl_al), .arsize(rl_as),
        .arvalid(rl_av), .arready(rl_ar), .rdata(rl_rd), .rresp(rl_rr), .rlast(rl_rl),
        .rvalid(rl_rv), .rready(rl_rrdy),
        .serve_clk(clk), .raddr(tile_reload_raddr),
        .color_o(tile_reload_color), .z_o(tile_reload_z), .reload_ready(tile_reload_ready)
    );

    // ================= behavioral 256-bit EMIF (shared mem; eclk) =================
    // Two independent write slaves (color/Z regions disjoint) + one read slave (reload).
    logic [255:0] mem [0:1023];
    // ---- color write slave ----
    typedef enum logic [1:0] {WA,WD,WB} ws_t;
    ws_t cws; logic [29:0] cbeat;
    assign cw_ar  = (cws==WA);
    assign cw_wr  = (cws==WD);
    assign cw_bv  = (cws==WB); assign cw_br = 2'b00;
    always_ff @(posedge eclk or negedge erst_n) begin
        if (!erst_n) cws<=WA;
        else case (cws)
            WA: if (cw_av) begin cbeat<=cw_aa[29:5]; cws<=WD; end
            WD: if (cw_wv) begin for (int by=0;by<32;by++) if (cw_ws[by]) mem[cbeat[9:0]][by*8+:8]<=cw_wd[by*8+:8]; cws<=WB; end
            WB: if (cw_brd) cws<=WA;
        endcase
    end
    // ---- Z write slave ----
    ws_t zws; logic [29:0] zbeat;
    assign zw_ar  = (zws==WA);
    assign zw_wr  = (zws==WD);
    assign zw_bv  = (zws==WB); assign zw_br = 2'b00;
    always_ff @(posedge eclk or negedge erst_n) begin
        if (!erst_n) zws<=WA;
        else case (zws)
            WA: if (zw_av) begin zbeat<=zw_aa[29:5]; zws<=WD; end
            WD: if (zw_wv) begin for (int by=0;by<32;by++) if (zw_ws[by]) mem[zbeat[9:0]][by*8+:8]<=zw_wd[by*8+:8]; zws<=WB; end
            WB: if (zw_brd) zws<=WA;
        endcase
    end
    // ---- reload read slave (a few-cycle latency to exercise async timing) ----
    typedef enum logic [1:0] {RA,RL,RV} rs_t;
    rs_t rs; logic [29:0] rbeat; logic [2:0] rlat;
    assign rl_ar = (rs==RA);
    assign rl_rv = (rs==RV); assign rl_rl = (rs==RV); assign rl_rr = 2'b00;
    assign rl_rd = mem[rbeat[9:0]];
    always_ff @(posedge eclk or negedge erst_n) begin
        if (!erst_n) begin rs<=RA; rlat<=0; end
        else case (rs)
            RA: if (rl_av) begin rbeat<=rl_aa[29:5]; rlat<=3'd4; rs<=RL; end
            RL: if (rlat==0) rs<=RV; else rlat<=rlat-1'b1;
            RV: if (rl_rrdy) rs<=RA;
        endcase
    end

    // ================= scanout capture (from Brick-1 TB) =================
    logic [7:0] cap_r[0:V_ACTIVE-1][0:H_ACTIVE-1], cap_g[0:V_ACTIVE-1][0:H_ACTIVE-1], cap_b[0:V_ACTIVE-1][0:H_ACTIVE-1];
    bit capture_armed; logic [31:0] hcnt_d, vcnt_d;
    always_ff @(posedge clk) begin
        if (!rst_n) begin hcnt_d<=0; vcnt_d<=0; end
        else begin hcnt_d<=32'(dut.u_pcrtc.hcnt); vcnt_d<=32'(dut.u_pcrtc.vcnt); end
    end
    always_ff @(posedge clk)
        if (rst_n && capture_armed && de && (vcnt_d<V_ACTIVE) && (hcnt_d<H_ACTIVE)) begin
            cap_r[vcnt_d][hcnt_d]<=r; cap_g[vcnt_d][hcnt_d]<=g; cap_b[vcnt_d][hcnt_d]<=b; end
    function automatic logic [23:0] px(input int y, input int x); px={cap_b[y][x],cap_g[y][x],cap_r[y][x]}; endfunction

    // reload handshake witnesses
    bit saw_reload_ready, saw_reload_start;
    always_ff @(posedge clk) begin
        if (!rst_n) begin saw_reload_ready<=0; saw_reload_start<=0; end
        else begin
            if (reload_start) saw_reload_start<=1'b1;
            if (tile_reload_ready) saw_reload_ready<=1'b1;
        end
    end

    // ---- DEBUG: trace region-A pixel (1,1) Z through the chain ----
    // tile-local index for (lx=1,ly=1) = ly*16+lx = 17. screen Z byte = (1*64+1)*4=0x104.
    // Z block in mem = (Z_BASE+0x104)>>5 = 0x88, lane = 0x104[4:2] = 1 (bits 63:32).
    int  reload_pass, zflush_evict;
    logic [31:0] zA_spilled_data [0:3];  // z_flush_data emitted for region-A screen addr 0x104, per eviction
    logic        zA_spilled_seen [0:3];
    logic [31:0] zA_reload_seen [0:3];   // tile_reload_z_i written into tile RAM idx 17, per reload pass
    logic [31:0] zA_mem_at_reload [0:3];
    bit          mem_has_8000;           // did 0x8000 ever appear anywhere in the Z region after batch1?
    // emif-domain: each time the reload fill completes a read of the region-A Z block (0x88),
    // capture what rdata word1 it got + how many fills ran.
    logic [31:0] fillZ_blk88 [0:7]; int fill_blk88_n; int fill_done_edges; logic rdone_d;
    always_ff @(posedge eclk) begin
        if (!erst_n) begin fill_blk88_n<=0; fill_done_edges<=0; rdone_d<=0; end
        else begin
            if (rl_rv && rl_rrdy && rl_aa==30'h0000_1100 && fill_blk88_n<8) begin
                fillZ_blk88[fill_blk88_n[2:0]]<=rl_rd[63:32]; fill_blk88_n<=fill_blk88_n+1;
            end
            rdone_d <= u_reload.reload_done;
            if (u_reload.reload_done && !rdone_d) fill_done_edges<=fill_done_edges+1;
        end
    end
    always_ff @(posedge clk) begin
        if (!rst_n) begin reload_pass<=0; zflush_evict<=0;
            zA_spilled_seen[0]<=0; zA_spilled_seen[1]<=0; zA_spilled_seen[2]<=0; zA_spilled_seen[3]<=0; end
        else begin
            if (reload_start) reload_pass<=reload_pass+1;
            // region A screen Z byte addr = (1*64+1)*4 = 0x104. Capture what's emitted for it.
            if (z_flush_emit && z_flush_addr[15:0]==16'h0104 && zflush_evict<4) begin
                zA_spilled_data[zflush_evict[1:0]]<=z_flush_data; zA_spilled_seen[zflush_evict[1:0]]<=1'b1;
            end
            // count evictions: a rising edge of TP_ZFLUSH entry (phase 6)
            if (tile_phase_o==3'd6 && dut.u_gs.tile_sweep_r==9'd0) zflush_evict<=zflush_evict+1;
            if (dut.u_gs.reload_wr_we && dut.u_gs.reload_wr_addr==8'd17 && reload_pass<4) begin
                zA_reload_seen[reload_pass[1:0]]   <= tile_reload_z;
                zA_mem_at_reload[reload_pass[1:0]] <= mem[10'h088][63:32];
            end
        end
    end

    int errors;
    task automatic chk(input bit c, input string m); if (!c) begin errors++; $display("[lpddr] FAIL: %s", m); end endtask

    logic [23:0] rA, rB;
    initial begin
        errors=0; core_go=0;
        // Ch323 — simulate a CLEAN BOOT from SCRUB LPDDR: fill the backing with GARBAGE (high Z).
        // The clean-Z bootstrap must still produce red-on-blue: batch-1 renders from the local
        // CLEAR (it does NOT reload this garbage), spills, and batch-2 reloads batch-1's spill.
        for (int i=0;i<1024;i++) mem[i]=256'h7EED7EED_7EED7EED_7EED7EED_7EED7EED_7EED7EED_7EED7EED_7EED7EED_7EED7EED;
        rst_n=0; erst_n=0; repeat(8) @(posedge eclk); erst_n=1; repeat(4) @(posedge clk); rst_n=1; repeat(8) @(posedge clk);
        @(negedge clk); core_go<=1'b1; @(negedge clk); core_go<=1'b0;
        $display("[lpddr] @%0t booted, waiting core_halt", $time);
        wait (core_halt==1'b1); repeat(4) @(posedge clk);
        wait (dma_done_seen==1'b1); repeat(10) @(posedge clk);
        if (dut.xfer_busy==1'b1) wait (dut.xfer_busy==1'b0);
        $display("[lpddr] @%0t waiting raster_active", $time);
        wait (dut.u_gs.raster_active==1'b1);
        wait (dut.u_gs.raster_active==1'b0); repeat(10) @(posedge clk);
        @(posedge dut.u_pcrtc.end_of_frame); @(posedge clk); capture_armed<=1'b1;
        @(posedge dut.u_pcrtc.end_of_frame); @(posedge clk); capture_armed<=1'b0;
        rA = px(1,1); rB = px(10,2);
        $display("[lpddr] regionA(1,1)=%06x regionB(10,2)=%06x", rA, rB);
        $display("[lpddr] writers: c_beats=%0d z_beats=%0d c_ovf=%0d z_ovf=%0d  reload: cbeats=%0d zbeats=%0d rderrs=%0d",
                 c_beats, z_beats, c_ovf, z_ovf, rl_cbeats, rl_zbeats, rl_rderrs);
        $display("[lpddr] handshake: saw_reload_start=%0d saw_reload_ready=%0d reload_passes=%0d", saw_reload_start, saw_reload_ready, reload_pass);
        $display("[lpddr] DEBUG region-A(idx17,screen 0x104) Z trace (evicts=%0d, reload passes=%0d):", zflush_evict, reload_pass);
        for (int p=0;p<4;p++) if (zA_spilled_seen[p]) $display("[lpddr]   evict %0d: z_flush emitted Z=0x%08x for region-A addr", p, zA_spilled_data[p]);
        for (int p=0;p<4;p++) $display("[lpddr]   reload slot %0d: idx17 served Z=0x%08x (mem lane1 was 0x%08x)", p, zA_reload_seen[p], zA_mem_at_reload[p]);
        // scan the whole Z region in mem for any 0x8000 word
        mem_has_8000 = 1'b0;
        for (int bk=10'h080; bk<10'h0F0; bk++) for (int wd=0; wd<8; wd++)
            if (mem[bk][wd*32+:32]==32'h0000_8000) mem_has_8000 = 1'b1;
        $display("[lpddr]   mem Z-region contains a 0x8000 word anywhere: %0d", mem_has_8000);
        $display("[lpddr]   fills completed (reload_done edges)=%0d, region-A Z-block reads=%0d:", fill_done_edges, fill_blk88_n);
        for (int p=0;p<fill_blk88_n && p<8;p++) $display("[lpddr]     fill read#%0d of block 0x88 word1 = 0x%08x", p, fillZ_blk88[p]);
        $display("[lpddr]   u_reload.z_ram[16..19] = %08x %08x %08x %08x (idx17 should be 0x8000)",
                 u_reload.z_ram[16], u_reload.z_ram[17], u_reload.z_ram[18], u_reload.z_ram[19]);
        $display("[lpddr]   u_reload final fill state rst_q=%0d row=%0d beat=%0d", u_reload.rst_q, u_reload.row, u_reload.beat);

        // NEGATIVE bootstrap proof: SPILL_FORCE_VALID=1 forces batch-1 to RELOAD the GARBAGE
        // (high-Z 0x7EED) backing instead of booting from clear. That garbage Z is enormous, so
        // the GEQUAL depth test rejects EVERY fragment -> region A is NOT painted red. This proves
        // the clean-Z bootstrap (skip reload on the first batch) is what makes the demo work.
        chk(rA!==COLOR1[23:0],
            $sformatf("NEG: region A = %06x but should NOT be red %06x — forced-valid garbage reload should defeat the render",
                      rA, COLOR1[23:0]));
        $display("[neg] region A=%06x region B=%06x (forced-valid + garbage backing: render defeated as expected)", rA, rB);

        if (errors==0) $display("[tb_gs_tile_spill_lpddr_neg] PASS");
        else           $display("[tb_gs_tile_spill_lpddr_neg] FAIL (errors=%0d)", errors);
        $finish;
    end
    initial begin #5000000; $display("[tb_gs_tile_spill_lpddr_neg] TIMEOUT"); $display("[tb_gs_tile_spill_lpddr_neg] FAIL"); $finish; end
endmodule
