// ============================================================================
// tb_gs_tile_spill_grid_lpddr.sv  (Ch324 — 4x4 multi-tile two-batch spill/reload)
//
// Scales the Ch323 single-tile integration proof (tb_gs_tile_spill_lpddr) to a
// 4x4 grid = 64x64 raster framebuffer (FBW=1). Wires the SAME real modules the
// de25 wires — bram-top gs_stub -> 2x gs_z_flush_writer (color/Z) -> behavioral
// 256-bit RASTER EMIF -> gs_tile_reload (RUNTIME per-tile reload_base) -> gs_stub
// serve port — across an async emif clock, driven by the bios/payload_tile_spill4x4
// fixture (P1 near color1 spills, P2 mid color2 reloads + depth-tests).
//
// The behavioral EMIF is the RASTER framebuffer: spill writes land at the global
// screen address each tile's flush produces, and gs_tile_reload gathers each tile
// back from COLOR/Z_BASE + reload_base. After the render we read the color region
// straight out of `mem` (= the LPDDR raster FB) and categorize the regions:
//   overlap (P1 region, x+y<64): reloaded Z=0x8000 -> P2 fails -> color1 (red), in
//                                MULTIPLE tiles (depth survival > 1 tile)
//   region B (64<=x+y<80, P2 only): reloaded clear -> P2 passes -> color2 (blue)
//   empty tiles (top row, bottom-right): clear
// Also traces per-tile reload_base values + spill/reload counters. This TB is the
// EMPIRICAL answer to "does the gs_stub tile FSM produce a grid two-batch
// spill/reload?" — it dumps the behavior before asserting.
// ============================================================================
`timescale 1ns/1ps

module tb_gs_tile_spill_grid_lpddr;
    localparam int H_ACTIVE = 64, V_ACTIVE = 64;
    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_4000;      // 16KiB color, then Z
    localparam logic [31:0] GARB = 32'h7EED7EED;

    logic clk=0;  always #5 clk  = ~clk;
    logic eclk=0; always #3 eclk = ~eclk;
    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(32*1024), .PSMCT32_SWIZZLE(1'b0),
        .COMBINED_TAZ(1'b1), .TILE_LOCAL(1'b1), .TILE_COLS(4), .TILE_ROWS(4),
        .TILE_SPILL_ENABLE(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'h10000), .FB_BYTES(32'h1000),
                        .TEX_BASE(30'h12000), .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'h10000), .FB_BYTES(32'h1000),
                        .TEX_BASE(30'h12000), .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 (per-tile base) =================
    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 RASTER EMIF =================
    logic [255:0] mem [0:2047];
    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[10:0]][by*8+:8]<=cw_wd[by*8+:8]; cws<=WB; end
            WB: if (cw_brd) cws<=WA;
        endcase
    end
    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[10:0]][by*8+:8]<=zw_wd[by*8+:8]; zws<=WB; end
            WB: if (zw_brd) zws<=WA;
        endcase
    end
    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[10: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

    // ---- witnesses: reload arms, per-tile reload_base values seen ----
    bit saw_reload_ready, saw_reload_start;
    int reload_pass; logic [29:0] base_seen [0:63]; int base_n;
    always_ff @(posedge clk) begin
        if (!rst_n) begin saw_reload_ready<=0; saw_reload_start<=0; reload_pass<=0; base_n<=0; end
        else begin
            if (reload_start) begin
                saw_reload_start<=1'b1; reload_pass<=reload_pass+1;
                if (base_n<64) begin base_seen[base_n]<=reload_base; base_n<=base_n+1; end
            end
            if (tile_reload_ready) saw_reload_ready<=1'b1;
        end
    end

    // ---- read the raster FB color region straight out of mem ----
    function automatic logic [23:0] cpx(input int gx, input int gy);
        logic [29:0] a; a = C_BASE + 30'((gy*64+gx)*4);
        cpx = mem[a >> 5][((a>>2)&3'd7)*32 +: 24];
    endfunction
    function automatic logic [31:0] zpx(input int gx, input int gy);
        logic [29:0] a; a = Z_BASE + 30'((gy*64+gx)*4);
        zpx = mem[a >> 5][((a>>2)&3'd7)*32 +: 32];
    endfunction

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

    int n_red, n_blue, n_clear, n_garb, n_other;
    int ovl_red, regB_blue;       // overlap pixels that are red; regionB pixels that are blue
    int empty_nonclear;            // top-row/empty-tile pixels that are NOT clear
    initial begin
        errors=0; core_go=0;
        for (int i=0;i<2048;i++) mem[i]={8{GARB}};   // clean-boot from garbage
        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("[grid] @%0t booted", $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);
        wait (dut.u_gs.raster_active==1'b1);
        $display("[grid] @%0t raster_active high", $time);
        wait (dut.u_gs.raster_active==1'b0); repeat(20) @(posedge clk);

        // counters + reload behavior
        $display("[grid] 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("[grid] saw_reload_start=%0d saw_reload_ready=%0d reload_passes=%0d distinct bases seen=%0d",
                 saw_reload_start, saw_reload_ready, reload_pass, base_n);
        for (int i=0;i<base_n && i<32;i++) $display("[grid]   reload pass %0d base=0x%06x", i, base_seen[i]);

        // categorize the whole 64x64 color FB
        n_red=0; n_blue=0; n_clear=0; n_garb=0; n_other=0; ovl_red=0; regB_blue=0; empty_nonclear=0;
        for (int gy=0; gy<64; gy++) for (int gx=0; gx<64; gx++) begin
            logic [23:0] c; c = cpx(gx,gy);
            if      (c==COLOR1[23:0]) n_red++;
            else if (c==COLOR2[23:0]) n_blue++;
            else if (c==24'd0)        n_clear++;
            else if (c==GARB[23:0])   n_garb++;
            else                      n_other++;
            // overlap = strictly inside P1 (x+y<64, x>=20,y>=20) ; expect red
            if (gx>=20 && gy>=20 && (gx+gy)<60) if (c==COLOR1[23:0]) ovl_red++;
            // region B = inside P2 but outside P1 (64<=x+y<78, x>=20,y>=20); expect blue
            if (gx>=20 && gy>=20 && (gx+gy)>66 && (gx+gy)<78) if (c==COLOR2[23:0]) regB_blue++;
            // empty top tile-row (gy<16): must be clear-or-untouched (not red/blue)
            if (gy<16) if (c==COLOR1[23:0] || c==COLOR2[23:0]) empty_nonclear++;
        end
        $display("[grid] FB color census: red=%0d blue=%0d clear=%0d garbage=%0d other=%0d", n_red,n_blue,n_clear,n_garb,n_other);
        $display("[grid] overlap-red(depth-survived)=%0d regionB-blue=%0d empty-toprow-painted=%0d", ovl_red, regB_blue, empty_nonclear);
        // sample a few tiles' overlap pixels to show depth survival spans >1 tile
        $display("[grid] sample px: (24,24)=%06x (36,24)=%06x (24,36)=%06x | regB (40,40)=%06x (50,28)=%06x | empty (4,4)=%06x (56,56)=%06x",
                 cpx(24,24), cpx(36,24), cpx(24,36), cpx(40,40), cpx(50,28), cpx(4,4), cpx(56,56));

        // ---- assertions (Codex Brick acceptance) ----
        chk(saw_reload_start && saw_reload_ready, "reload never armed/readied across the grid");
        chk(!c_ovf && !z_ovf, "spill FIFO overflow");
        chk(c_beats>0 && z_beats>0 && rl_cbeats>0 && rl_zbeats>0, "spill/reload wrote/read 0 beats");
        chk(ovl_red >= 3, $sformatf("depth survival weak: only %0d overlap pixels kept color1 (expect across >1 tile)", ovl_red));
        chk(regB_blue >= 3, $sformatf("region B weak: only %0d pixels accepted color2", regB_blue));
        chk(empty_nonclear==0, $sformatf("%0d empty top-row pixels were painted (should stay clear)", empty_nonclear));

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