// ============================================================================
// gs_texture_cache.sv  (Ch322 — PREFILLED texture cache, correctness-first)
//
// Proves texture bytes can live in FPGA-private LPDDR4B and be consumed by the
// GS sampler through an on-chip RAM at the EXISTING 1-cycle texel latency.
//
// This is a PREFILLED cache, NOT a demand cache. The whole known texture is
// filled from LPDDR into an on-chip RAM ONCE, before rendering (mirroring the
// Ch321 line-buffer trick: warm a bounded buffer, then serve at native latency).
// Every sampler read is therefore a HIT at 1-cycle latency — the nearest-path
// sampler's fixed-latency contract (gs_texture_unit valid_pipe, RD_LATENCY=1) is
// preserved with ZERO pipeline/stall surgery. Demand miss/stall is explicitly
// DEFERRED to a later chapter (it would be a raster-walker pipeline redesign).
//
// Two clock domains (same split as gs_lpddr_scanout_lb):
//   axi_clk    (emif_clk) — fill FSM: single-beat 256-bit reads (arlen=0, the
//                           only read pattern proven on this EMIF) from LPDDR.
//   sample_clk (design)   — the sampler's texel read port; 1-cycle registered,
//                           byte-identical timing to the vram_bram_stub read2.
//
// The fill is one-shot before raster, so the on-chip RAM is static when the
// sampler reads it (write side idle) — a plain dual-clock simple-dual-port RAM,
// no read/write CDC hazard. `fill_done` is 2-FF synced into sample_clk so the
// read mux only goes live after the texture is fully resident.
// ============================================================================
`timescale 1ns/1ps

module gs_texture_cache #(
    parameter [29:0] LPDDR_TEX_BASE = 30'd0,   // byte base of the texture in LPDDR4B
    parameter [31:0] TEX_VRAM_BASE  = 32'd2048,// VRAM byte base the sampler addresses (TBP0*256)
    parameter int    TEX_BYTES      = 256,     // texture size in bytes (8x8 PSMCT32 = 256)
    parameter int    N_BEATS        = 8         // TEX_BYTES / 32
)(
    // ---- AXI read clock domain (emif_clk) — fill side ----
    input  logic         axi_clk,
    input  logic         axi_rst_n,
    input  logic         fill_start,    // TOGGLE (bridge domain, CDC-synced): each edge (re)fills
    output logic         fill_done,     // texture fully resident (until the next fill arm)
    output logic [31:0]  fill_beats,    // beats completed (cumulative)
    output logic [31:0]  fill_bytes,    // bytes filled (cumulative)
    output logic [31:0]  rd_errs,       // non-OKAY read responses (cumulative)
    output logic [31:0]  fill_crc,      // Ch352 — sum32 of EVERY 32-bit word actually written into tex_mem. The
                                        //   host compares this to the file's sum32 to PROVE tex_mem integrity on
                                        //   silicon (the LPDDR readback only proves LPDDR, not the cache contents).

    // ---- AXI4 read channel to the EMIF user port (axi_clk, 256-bit) ----
    output logic [29:0]  araddr,
    output logic [1:0]   arburst,
    output logic [6:0]   arid,
    output logic [7:0]   arlen,
    output logic [2:0]   arsize,
    output logic         arvalid,
    input  logic         arready,
    input  logic [255:0] rdata,
    input  logic [1:0]   rresp,
    input  logic         rlast,
    input  logic         rvalid,
    output logic         rready,

    // ---- sampler clock domain (design_clk) — texel read port ----
    input  logic         sample_clk,
    input  logic         tex_rd_en,     // sampler issues a texel read this cycle
    input  logic [31:0]  tex_rd_addr,   // VRAM byte address (TEX_VRAM_BASE + offset)
    output logic [31:0]  tex_rd_data,   // 1-cycle REGISTERED texel word (matches read2)
    output logic         tex_ready      // fill_done synced into sample_clk (mux gate)
);
    localparam int TEX_WORDS = TEX_BYTES/4;            // 32-bit words in the cache
    localparam int WIDX_BITS = (TEX_WORDS <= 1) ? 1 : $clog2(TEX_WORDS);
    localparam int BIDX_BITS = (N_BEATS <= 1) ? 1 : $clog2(N_BEATS);

    assign arburst = 2'b01;   // INCR
    assign arid    = 7'd4;    // distinct id: writer=0, probe=1, frame-cache=2, line-buf=3, tex-fill=4
    assign arlen   = 8'd0;    // SINGLE-BEAT (the only AXI read pattern proven on this EMIF)
    assign arsize  = 3'b101;  // 32 bytes

    // On-chip texture RAM: written by the fill FSM (axi_clk), read by the sampler
    // (sample_clk). One-shot warm fill => static during reads => no CDC hazard.
    //
    // One ordinary 32-bit simple-dual-port RAM. History of cache geometry vs Quartus Place:
    //   flat 8-write array      -> exploded to flops (707k ALUT)
    //   8x 8192x32 banks        -> width fragmentation, 344/358, 9h Place thrash
    //   1x 8192x256 RAM         -> count OK (320/358) but one ~104-M20K macro too WIDE -> Place 40min+, no QDB
    //   2x 8192x128 halves      -> still rigid, Place 50min+ no progress
    //   4x 8192x64 banks       -> 328/358; Place still stalled with ample RAM after read2 was removed.
    //   1x 65536x32 (HERE)     -> latch each AXI beat, drain 8 lanes over 8 axi_clk cycles.
    // Serializing the fill removes the multi-bank/multi-write geometry while preserving the sampler's
    // one-cycle registered 32-bit read. The one-shot fill is still tiny compared with board startup.
    (* ramstyle = "M20K" *) logic [31:0] tex_mem [0:TEX_WORDS-1];

    // ================= fill side (axi_clk) =================
    typedef enum logic [2:0] { F_IDLE, F_AR, F_R, F_DRAIN, F_WRITE, F_DONE } fstate_t;
    fstate_t fst;
    logic [$clog2(N_BEATS):0] beat;     // 0..N_BEATS
    logic [255:0] fill_data_q;
    logic [2:0]   fill_lane;
    logic [WIDX_BITS-1:0] fill_word_base;
    wire  [WIDX_BITS-1:0] fill_word_idx = fill_word_base + WIDX_BITS'(fill_lane);
    // Ch358 (Codex) — registered RESOLVED drain word + RAM index. The 26.1 STA showed fill_lane launching into
    // tex_mem's data port: the dynamic 256->32 mux (fill_data_q[fill_lane*32+:32]) fed the RAM write directly.
    // F_DRAIN now only registers the SELECTED word/index; F_WRITE commits that register to tex_mem next cycle
    // (2 cycles/lane — harmless one-shot fill time). fill_crc accumulates the COMMITTED word, semantics unchanged.
    // These payload registers are deliberately unreset.  F_WRITE is reachable only after F_DRAIN has loaded both,
    // so reset values are unobservable; keeping them out of the 4k-fanout EMIF calibration reset removes that reset
    // from the duplicated RAM-address launch registers at 310 MHz.
    logic [31:0]          drain_word_q;
    logic [WIDX_BITS-1:0] drain_idx_q;
    // fill_start is an EDGE/TOGGLE (bridge toggles it on each arm), CDC-synced here so the
    // cache is RE-FILLABLE: each arm reloads the texture (lets the HPS re-stage a different
    // texture without a board reset). 3-FF sync + edge-detect, like the read/write probes.
    logic [2:0] fs_sync;
    wire        fs_edge = (fs_sync[2] != fs_sync[1]);

    always_ff @(posedge axi_clk) begin
        if (!axi_rst_n) begin
            fst <= F_IDLE; araddr <= '0; arvalid <= 1'b0; rready <= 1'b0;
            beat <= '0; fill_done <= 1'b0; fill_beats <= 32'd0; fill_bytes <= 32'd0;
            rd_errs <= 32'd0; fs_sync <= 3'd0; fill_data_q <= '0;
            fill_lane <= 3'd0; fill_word_base <= '0; fill_crc <= 32'd0;
        end else begin
            fs_sync <= {fs_sync[1:0], fill_start};
            case (fst)
                F_IDLE: begin
                    if (fs_edge) begin
                        araddr     <= LPDDR_TEX_BASE;
                        beat       <= '0;
                        fill_done  <= 1'b0;          // re-arm: drop ready until reloaded
                        fill_beats <= 32'd0;
                        fill_bytes <= 32'd0;
                        rd_errs    <= 32'd0;
                        fill_crc   <= 32'd0;     // Ch352 — restart the tex_mem integrity sum for this fill
                        arvalid    <= 1'b1;
                        fst        <= F_AR;
                    end
                end
                F_AR: begin
                    if (arready) begin
                        arvalid <= 1'b0;
                        rready  <= 1'b1;
                        fst     <= F_R;
                    end
                end
                F_R: begin
                    if (rvalid) begin
                        // Capture the AXI beat, then issue one M20K-native 32-bit write per cycle.
                        fill_data_q  <= rdata;
                        fill_lane    <= 3'd0;
                        fill_word_base <= {beat[BIDX_BITS-1:0], 3'b000};
                        if (rresp != 2'b00) rd_errs <= rd_errs + 32'd1;
                        rready <= 1'b0;
                        fst    <= F_DRAIN;
                    end
                end
                F_DRAIN: begin
                    fst          <= F_WRITE;
                end
                F_WRITE: begin
                    // COMMIT half: registered word -> M20K; CRC over the word actually committed.
                    tex_mem[drain_idx_q] <= drain_word_q;
                    fill_crc <= fill_crc + drain_word_q;   // sum32 over the words written
                    if (fill_lane == 3'd7) begin
                        fill_beats <= fill_beats + 32'd1;
                        fill_bytes <= fill_bytes + 32'd32;
                        if (beat == N_BEATS-1) begin
                            fill_done <= 1'b1;
                            fst       <= F_DONE;
                        end else begin
                            beat    <= beat + 1'b1;
                            araddr  <= araddr + 30'd32;   // next single-beat read
                            arvalid <= 1'b1;
                            fst     <= F_AR;
                        end
                    end else begin
                        fill_lane <= fill_lane + 3'd1;
                        fst       <= F_DRAIN;
                    end
                end
                F_DONE: begin
                    // resident until the next arm. A fresh fill_start edge re-loads the
                    // texture (e.g. HPS re-stages a different one) — start a new fill.
                    if (fs_edge) begin
                        araddr     <= LPDDR_TEX_BASE;
                        beat       <= '0;
                        fill_done  <= 1'b0;
                        fill_beats <= 32'd0;
                        fill_bytes <= 32'd0;
                        rd_errs    <= 32'd0;
                        fill_crc   <= 32'd0;     // Ch352 — restart the tex_mem integrity sum for this fill
                        arvalid    <= 1'b1;
                        fst        <= F_AR;
                    end
                end
                default: fst <= F_IDLE;
            endcase
        end
    end

    // Control-free payload boundary: observability is controlled by the reset FSM, not by reset on the data itself.
    always_ff @(posedge axi_clk) begin
        if (fst == F_DRAIN) begin
            drain_word_q <= fill_data_q[fill_lane*32 +: 32];
            drain_idx_q  <= fill_word_idx;
        end
    end

    // ================= sampler side (sample_clk) =================
    // 1-cycle REGISTERED read, identical timing to vram_bram_stub.read2:
    // present (tex_rd_addr) when tex_rd_en, data lands next cycle.
    wire [31:0] word_off = (tex_rd_addr - TEX_VRAM_BASE) >> 2;
    wire [WIDX_BITS-1:0] rd_word = word_off[WIDX_BITS-1:0];
    always_ff @(posedge sample_clk) begin
        if (tex_rd_en) tex_rd_data <= tex_mem[rd_word];
    end

    // fill_done -> sample_clk (2-FF). The read mux only goes live once warm.
    logic [1:0] done_sync;
    always_ff @(posedge sample_clk) done_sync <= {done_sync[0], fill_done};
    assign tex_ready = done_sync[1];
endmodule
