// ============================================================================
// tb_gs_texture_cache.sv  (Ch322 Brick 1)
//
// Proves the prefilled texture cache: fill an 8x8 PSMCT32 texture (256 B = 8
// single-beat 256-bit reads) from a tiny EMIF-ish read model into the on-chip
// RAM, then verify:
//   (1) fill_done asserts, fill_beats=8, fill_bytes=256, rd_errs=0
//   (2) every cached word == the source word (sampler-side 1-cycle reads)
//   (3) the read is 1-cycle REGISTERED (data lands the cycle AFTER tex_rd_en)
//   (4) a forced non-OKAY rresp increments rd_errs
// ============================================================================
`timescale 1ns/1ps

module tb_gs_texture_cache;
    localparam int TEX_BYTES = 256;
    localparam int N_BEATS   = 8;
    localparam int TEX_WORDS = TEX_BYTES/4;        // 64
    localparam [29:0] LPDDR_TEX_BASE = 30'h0010_0000;
    localparam [31:0] TEX_VRAM_BASE  = 32'd2048;

    logic axi_clk = 0, sample_clk = 0, axi_rst_n = 0;
    always #5  axi_clk    = ~axi_clk;    // 100 MHz-ish
    always #7  sample_clk = ~sample_clk; // asynchronous, slower

    // fill control
    logic        fill_start;
    logic        fill_done;
    logic [31:0] fill_beats, fill_bytes, rd_errs;

    // AXI read channel
    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;

    // sampler read port
    logic        tex_rd_en;
    logic [31:0] tex_rd_addr, tex_rd_data;
    logic        tex_ready;

    // golden source. Ch443: every byte lane carries DISTINCT data — different per index
    // AND different across the four banks (distinct XOR keys + distinct bases) — so the
    // four-bank tex_mem split is genuinely exercised: a bank swap, misroute, or dropped
    // bank produces a wrong reconstructed byte that the full-word + per-bank checks catch.
    // (The old 0xC0DE_0000|i had constant bytes 2/3, which a bank fault could hide.)
    function automatic [31:0] tex_word(input int i);
        tex_word = { 8'(((i*4 + 3) & 32'hFF) ^ 32'hA3),
                     8'(((i*4 + 2) & 32'hFF) ^ 32'h5C),
                     8'(((i*4 + 1) & 32'hFF) ^ 32'h91),
                     8'(((i*4 + 0) & 32'hFF) ^ 32'h2E) };
    endfunction

    // force a bad rresp on a chosen beat to exercise rd_errs (set <0 to disable)
    int err_beat = -1;

    gs_texture_cache #(
        .LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
        .TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)
    ) dut (
        .axi_clk(axi_clk), .axi_rst_n(axi_rst_n),
        .fill_start(fill_start), .fill_done(fill_done),
        .fill_beats(fill_beats), .fill_bytes(fill_bytes), .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),
        .sample_clk(sample_clk), .tex_rd_en(tex_rd_en),
        .tex_rd_addr(tex_rd_addr), .tex_rd_data(tex_rd_data), .tex_ready(tex_ready)
    );

    // ---- single-beat EMIF read model (arlen=0 only), ~ a few cycles latency ----
    // returns 8 words per 256-bit beat from the golden source, indexed by araddr.
    typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t;
    sstate_t sst;
    logic [29:0] beat_addr; logic [3:0] dly; int beat_idx;
    always_ff @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            sst <= S_IDLE; arready <= 1'b0; rvalid <= 1'b0; rlast <= 1'b0;
            rresp <= 2'b00; rdata <= '0; dly <= '0;
        end else begin
            arready <= 1'b0; rvalid <= 1'b0; rlast <= 1'b0;
            case (sst)
                S_IDLE: if (arvalid) begin
                    beat_addr <= araddr; arready <= 1'b1;
                    beat_idx  <= (araddr - LPDDR_TEX_BASE) >> 5;  // 32 B/beat
                    dly <= 4'd3; sst <= S_WAIT;
                end
                S_WAIT: if (dly==0) sst <= S_DATA; else dly <= dly - 1'b1;
                S_DATA: if (rready) begin
                    for (int w=0; w<8; w++) rdata[w*32 +: 32] <= tex_word(beat_idx*8 + w);
                    rresp  <= (beat_idx == err_beat) ? 2'b10 : 2'b00; // SLVERR on chosen beat
                    rvalid <= 1'b1; rlast <= 1'b1;
                    sst    <= S_IDLE;
                end
            endcase
        end
    end

    int errors = 0;
    task automatic check(input bit cond, input string msg);
        if (!cond) begin errors++; $display("[texcache] FAIL: %s", msg); end
    endtask

    // sampler 1-cycle read helper (sample_clk domain)
    task automatic read_word(input int widx, output logic [31:0] got);
        @(posedge sample_clk);
        tex_rd_addr <= TEX_VRAM_BASE + widx*4; tex_rd_en <= 1'b1;
        @(posedge sample_clk);
        tex_rd_en <= 1'b0;          // data for THIS address lands now (1-cycle registered)
        @(posedge sample_clk);      // sample the registered output
        got = tex_rd_data;
    endtask

    logic [31:0] got;
    initial begin
        fill_start = 0; tex_rd_en = 0; tex_rd_addr = 0;
        repeat (4) @(posedge axi_clk); axi_rst_n = 1;
        repeat (2) @(posedge axi_clk);

        // ---- pass 1: clean fill ----
        // fill_start is an EDGE/TOGGLE: flip once (0->1) and HOLD — a 0->1->0 pulse would
        // trigger a second fill on the falling edge. Pass 2's reset re-creates the edge.
        @(posedge axi_clk); fill_start <= 1'b1;
        // wait for fill_done
        begin int g=0; while (!fill_done && g<2000) begin @(posedge axi_clk); g++; end end
        check(fill_done,                "fill_done never asserted");
        check(fill_beats == N_BEATS,    $sformatf("fill_beats=%0d exp %0d", fill_beats, N_BEATS));
        check(fill_bytes == TEX_BYTES,  $sformatf("fill_bytes=%0d exp %0d", fill_bytes, TEX_BYTES));
        check(rd_errs   == 32'd0,       $sformatf("rd_errs=%0d exp 0", rd_errs));

        // wait for tex_ready to cross into sample_clk
        begin int g=0; while (!tex_ready && g<200) begin @(posedge sample_clk); g++; end end
        check(tex_ready, "tex_ready never synced");

        // ---- verify every word via the sampler 1-cycle read port ----
        // Full reconstructed word AND per-byte-lane pinpoint. The distinct-per-byte golden
        // exercises all four byte lanes of the monolithic 32-bit word (Ch443).
        for (int i=0; i<TEX_WORDS; i++) begin
            logic [31:0] exp;
            read_word(i, got);
            exp = tex_word(i);   // temp: iverilog forbids part-select on a function-call result
            check(got == exp,          $sformatf("word[%0d]=%08x exp %08x", i, got, exp));
            check(got[7:0]   === exp[7:0],   $sformatf("lane0 byte[%0d]=%02x exp %02x", i, got[7:0],   exp[7:0]));
            check(got[15:8]  === exp[15:8],  $sformatf("lane1 byte[%0d]=%02x exp %02x", i, got[15:8],  exp[15:8]));
            check(got[23:16] === exp[23:16], $sformatf("lane2 byte[%0d]=%02x exp %02x", i, got[23:16], exp[23:16]));
            check(got[31:24] === exp[31:24], $sformatf("lane3 byte[%0d]=%02x exp %02x", i, got[31:24], exp[31:24]));
        end

        $display("[texcache] clean: fill_done=%0d beats=%0d bytes=%0d rd_errs=%0d words_checked=%0d errors=%0d",
                 fill_done, fill_beats, fill_bytes, rd_errs, TEX_WORDS, errors);

        // ---- pass 2: rd_errs exercise (reset, force SLVERR on beat 3) ----
        // The reset clears the cache's fill_start edge-sync; with fill_start still held 1
        // from pass 1, the post-reset re-sync re-creates the edge and re-fills.
        axi_rst_n = 0; err_beat = 3; repeat (3) @(posedge axi_clk); axi_rst_n = 1;
        repeat (2) @(posedge axi_clk);
        begin int g=0; while (!fill_done && g<2000) begin @(posedge axi_clk); g++; end end
        check(rd_errs == 32'd1, $sformatf("forced-err rd_errs=%0d exp 1", rd_errs));
        $display("[texcache] err-inject: rd_errs=%0d (exp 1)", rd_errs);

        if (errors==0) $display("[tb_gs_texture_cache] PASS");
        else           $display("[tb_gs_texture_cache] FAIL (%0d errors)", errors);
        $finish;
    end

    initial begin #500000; $display("[tb_gs_texture_cache] TIMEOUT"); $finish; end
endmodule
