// ============================================================================
// tb_gs_tile_reload_grid.sv  (Ch324 Brick 1 — per-tile reload base)
//
// Proves gs_tile_reload's RUNTIME per-tile `reload_base` against a RASTER/linear
// LPDDR framebuffer (the Ch324 layout). A 4x4 grid of 16x16 tiles = a 64x64
// PSMCT32 framebuffer (FBW=1 -> 64 px/row -> STRIDE_BYTES = 64*4 = 256). The
// behavioral EMIF holds the WHOLE framebuffer (color at COLOR_BASE, Z at Z_BASE),
// pre-filled per global pixel. For each of the 16 tiles we drive
//   reload_base = ((tile_oy*(FBW*64)) + tile_ox)*4   (the SAME formula gs_stub uses)
// arm the fill, and verify the engine gathered EXACTLY that tile from its global
// origin: every served (color,Z) for index 0..255 == the framebuffer at
// (tile_ox+tx, tile_oy+ty). Checks Codex's Brick-1 guardrails:
//   - reload base changes per tile and matches the tile's global origin (the first
//     AR of each fill == COLOR_BASE + reload_base; sampled for all 16 tiles)
//   - per-tile gather is correct (served data == raster FB at that tile)
//   - an EMPTY tile (raster region = clear) reloads as clear
//   - HPS/LPDDR-facing address expectations for sample tiles 0/5/10/15
// ============================================================================
`timescale 1ns/1ps

module tb_gs_tile_reload_grid;
    localparam int TILE_W=16, TILE_H=16, STRIDE_BYTES=256, ROW_BEATS=2, COLOR_W=32;
    localparam int FBW64 = 64;                         // FBW=1 -> 64 px/row
    localparam int COLS=4, ROWS=4, N_ENTRIES=TILE_W*TILE_H;
    localparam [29:0] COLOR_BASE = 30'd0;
    localparam [29:0] Z_BASE     = 30'h0000_4000;      // distinct, past the 16KB color region
    localparam int EMPTY_TILE = 9;                     // (col=1,row=2) — reloads as clear
    localparam [31:0] CLEAR_C = 32'h0000_0000, CLEAR_Z = 32'h0000_4000;

    logic axi_clk=0, serve_clk=0, axi_rst_n=0;
    always #5  axi_clk   = ~axi_clk;
    always #7  serve_clk = ~serve_clk;                 // asynchronous

    logic        reload_start, reload_done;
    logic [29:0] reload_base;
    logic [31:0] color_beats, z_beats, rd_errs;
    logic [29:0] araddr; logic [1:0] arburst; logic [6:0] arid;
    logic [7:0]  arlen;  logic [2:0] arsize;  logic arvalid, arready;
    logic [255:0] rdata; logic [1:0] rresp;   logic rlast, rvalid, rready;
    logic [7:0]  raddr; logic [31:0] color_o, z_o; logic reload_ready;

    // per-global-pixel reference patterns (tile-distinct), with one CLEAR tile.
    function automatic int tile_of(input int gx, input int gy);
        tile_of = (gy/16)*COLS + (gx/16);
    endfunction
    function automatic [31:0] cref(input int gx, input int gy);
        if (tile_of(gx,gy)==EMPTY_TILE) cref = CLEAR_C;
        else cref = {8'(tile_of(gx,gy)), 8'(gy), 8'(gx), 8'hC0};
    endfunction
    function automatic [31:0] zref(input int gx, input int gy);
        if (tile_of(gx,gy)==EMPTY_TILE) zref = CLEAR_Z;
        else zref = 32'h0000_1000 + 32'(tile_of(gx,gy));   // distinct Z per tile
    endfunction
    // the per-tile reload base Codex specified: ((tile_oy*(FBW*64)) + tile_ox)*4
    function automatic [29:0] base_of(input int col, input int row);
        base_of = 30'(((row*16)*FBW64 + (col*16)) * 4);
    endfunction

    gs_tile_reload #(
        .COLOR_BASE(COLOR_BASE), .Z_BASE(Z_BASE), .TILE_W(TILE_W), .TILE_H(TILE_H),
        .STRIDE_BYTES(STRIDE_BYTES), .ROW_BEATS(ROW_BEATS), .COLOR_W(COLOR_W)
    ) dut (
        .axi_clk(axi_clk), .axi_rst_n(axi_rst_n), .reload_start(reload_start), .reload_base(reload_base),
        .reload_done(reload_done), .color_beats(color_beats), .z_beats(z_beats), .rd_errs(rd_errs),
        .araddr(araddr), .arburst(arburst), .arid(arid), .arlen(arlen), .arsize(arsize),
        .arvalid(arvalid), .arready(arready), .rdata(rdata), .rresp(rresp), .rlast(rlast),
        .rvalid(rvalid), .rready(rready),
        .serve_clk(serve_clk), .raddr(raddr), .color_o(color_o), .z_o(z_o), .reload_ready(reload_ready)
    );

    // behavioral 256-bit EMIF: whole 64x64 raster framebuffer (color + Z regions).
    logic [255:0] mem [0:2047];
    initial begin
        for (int i=0;i<2048;i++) mem[i]=256'd0;
        for (int gy=0; gy<64; gy++) for (int gx=0; gx<64; gx++) begin
            logic [29:0] ca, za;
            ca = COLOR_BASE + 30'((gy*FBW64 + gx)*4);
            za = Z_BASE     + 30'((gy*FBW64 + gx)*4);
            mem[ca >> 5][((ca >> 2) & 3'd7)*32 +: 32] = cref(gx,gy);
            mem[za >> 5][((za >> 2) & 3'd7)*32 +: 32] = zref(gx,gy);
        end
    end
    // first AR address captured per fill (= what the engine drives for row 0 color).
    logic [29:0] first_ar; logic first_seen;
    typedef enum logic [1:0] { S_IDLE,S_WAIT,S_DATA } sst_t; sst_t sst;
    logic [29:0] ba; logic [3:0] dly;
    always_ff @(posedge axi_clk) begin
        if (!axi_rst_n) begin sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=0; rdata<=0; dly<=0;
        end else begin
            arready<=0; rvalid<=0; rlast<=0;
            case (sst)
                S_IDLE: if (arvalid) begin
                    ba<=araddr; arready<=1; dly<=4'd3; sst<=S_WAIT;
                    if (!first_seen) begin first_ar<=araddr; first_seen<=1'b1; end
                end
                S_WAIT: if (dly==0) sst<=S_DATA; else dly<=dly-1'b1;
                S_DATA: if (rready) begin
                    rdata<=mem[ba >> 5]; rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE;
                end
            endcase
        end
    end

    int errors=0;
    task automatic chk(input bit c, input string m); if (!c) begin errors++; $display("[reloadgrid] FAIL: %s", m); end endtask
    task automatic rd(input int idx, output logic [31:0] cg, output logic [31:0] zg);
        @(posedge serve_clk); raddr <= idx[7:0];
        @(posedge serve_clk); @(posedge serve_clk); cg = color_o; zg = z_o;
    endtask

    logic [31:0] cg, zg; logic [29:0] seen_base[0:15]; int tiles_ok;
    initial begin
        reload_start=0; raddr=0; reload_base=30'd0; first_seen=0;
        repeat(4) @(posedge axi_clk); axi_rst_n=1; repeat(2) @(posedge axi_clk);
        tiles_ok=0;

        for (int t=0; t<COLS*ROWS; t++) begin
            int col, row, gx0, gy0; logic [29:0] eb;
            col = t % COLS; row = t / COLS; gx0 = col*16; gy0 = row*16;
            eb  = base_of(col, row);

            // arm this tile's fill with its per-tile base (PULSE the strobe). reload_done is still
            // HIGH from the previous tile, so wait for it to FALL (this fill accepted) then RISE
            // (this fill complete) — otherwise a stale done would pass instantly.
            first_seen = 1'b0;
            @(posedge axi_clk) begin reload_base <= eb; reload_start <= 1'b1; end
            @(posedge axi_clk) reload_start <= 1'b0;
            begin int g=0; while(reload_done && g<400) begin @(posedge axi_clk); g++; end end   // fall
            begin int g=0; while(!reload_done && g<40000) begin @(posedge axi_clk); g++; end end  // rise
            chk(reload_done, $sformatf("tile %0d reload_done never asserted", t));
            chk(color_beats==32 && z_beats==32, $sformatf("tile %0d beats c=%0d z=%0d exp 32/32", t, color_beats, z_beats));
            begin int g=0; while(!reload_ready && g<400) begin @(posedge serve_clk); g++; end end
            chk(reload_ready, $sformatf("tile %0d reload_ready never synced", t));

            // GUARDRAIL: base changes per tile + the engine drove the matching global origin.
            seen_base[t] = first_ar;
            chk(first_ar === (COLOR_BASE + eb),
                $sformatf("tile %0d first AR=0x%06x exp COLOR_BASE+base 0x%06x (per-tile origin)", t, first_ar, COLOR_BASE+eb));

            // per-tile gather: every served pixel == the raster FB at this tile's global origin.
            for (int i=0;i<N_ENTRIES;i++) begin
                int tx, ty;
                tx = i % 16; ty = i / 16;
                rd(i, cg, zg);
                chk(cg===cref(gx0+tx, gy0+ty),
                    $sformatf("tile %0d px(%0d,%0d) color=%08x exp %08x", t, tx, ty, cg, cref(gx0+tx,gy0+ty)));
                chk(zg===zref(gx0+tx, gy0+ty),
                    $sformatf("tile %0d px(%0d,%0d) z=%08x exp %08x", t, tx, ty, zg, zref(gx0+tx,gy0+ty)));
            end
            if (t==EMPTY_TILE) begin
                rd(0, cg, zg);
                chk(cg===CLEAR_C && zg===CLEAR_Z, $sformatf("EMPTY tile %0d not clear: c=%08x z=%08x", t, cg, zg));
            end
            tiles_ok++;
        end

        // base monotonic/distinct across the grid (no two tiles share an origin).
        for (int a=0;a<16;a++) for (int b=a+1;b<16;b++)
            chk(seen_base[a] !== seen_base[b], $sformatf("tiles %0d and %0d share base 0x%06x", a, b, seen_base[a]));
        // sample-tile address expectations (the HPS/LPDDR-facing offsets).
        chk(seen_base[0]  === COLOR_BASE + base_of(0,0), "sample tile 0 base");
        chk(seen_base[5]  === COLOR_BASE + base_of(1,1), "sample tile 5 base");
        chk(seen_base[10] === COLOR_BASE + base_of(2,2), "sample tile 10 base");
        chk(seen_base[15] === COLOR_BASE + base_of(3,3), "sample tile 15 base");

        $display("[reloadgrid] tiles=%0d empty_tile=%0d base0=0x%06x base5=0x%06x base10=0x%06x base15=0x%06x errors=%0d",
                 tiles_ok, EMPTY_TILE, seen_base[0], seen_base[5], seen_base[10], seen_base[15], errors);
        if (errors==0) $display("[tb_gs_tile_reload_grid] PASS"); else $display("[tb_gs_tile_reload_grid] FAIL (%0d errors)", errors);
        $finish;
    end
    initial begin #60000000; $display("[tb_gs_tile_reload_grid] TIMEOUT"); $finish; end
endmodule
