// retroDE_ps2 — tb_top_psmct32_raster_demo_bram_lpddrtex  (Ch322 Brick 2)
//
// END-TO-END proof of the PREFILLED LPDDR texture cache: the SAME textured
// triangle as tb_top_psmct32_raster_demo_bram_tritex, but with GS_LPDDR_TEX=1
// so the texel-fetch port is muxed to gs_texture_cache, warmed from a behavioral
// LPDDR (EMIF-ish) read model BEFORE raster.
//
// STRONG PROOF: after the EE bootlet uploads the texture to VRAM, the TB CORRUPTS
// the VRAM texture region. The cache was already warmed from the (good) LPDDR
// model, so if the rendered triangle still shows the correct quadrant texels, the
// texels provably came from LPDDR through the cache — not from BRAM. Acceptance:
//   - tex_cache_ready high BEFORE raster, fill_done, fill_beats=64, rd_errs=0
//   - every interior pixel == the expected tex_demo_texel (matches BRAM ref)
//   - >1 distinct colour (texturing not collapsed)
`timescale 1ns/1ps

module tb_top_psmct32_raster_demo_bram_lpddrtex;

    localparam int H_ACTIVE = 16;
    localparam int V_ACTIVE = 8;

    // cache geometry: tritex texture is 8x8 PSMCT32, TBW=1 (64-texel/256-B row
    // stride) at TBP0=8 -> VRAM byte base 2048. Addressed range spans 8 rows *
    // 256 B = 2048 B = 64 single-beat (32 B) reads.
    localparam [29:0] LPDDR_TEX_BASE = 30'h0010_0000;
    localparam int    TEX_VRAM_BASE  = 2048;
    localparam int    TEX_BYTES      = 2048;
    localparam int    N_BEATS        = 64;
    localparam int    TEX_WORDS      = TEX_BYTES/4;   // 512

    logic clk; logic rst_n;
    initial clk = 1'b0; always #5 clk = ~clk;

    logic        core_go;
    logic [7:0]  r, g, b;
    logic        hsync, vsync, de;
    logic        core_halt, dma_done_seen, frame_seen, raster_overflow;
    logic        frame_toggle, dma_done_toggle;

    // bram-top <-> texture-cache tap
    logic        gs_tex_rd_en_o;
    logic [31:0] gs_tex_rd_addr_o;
    logic [31:0] tex_cache_data;
    logic        tex_cache_ready;
    logic [31:0] tex_cache_hits, tex_bram_hits;   // Ch322 texel-source counters

    // texture-cache AXI read channel <-> behavioral LPDDR model
    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        fill_start, fill_done;
    logic [31:0] fill_beats, fill_bytes, tex_rd_errs;

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE(H_ACTIVE), .V_ACTIVE(V_ACTIVE), .PSMCT32_SWIZZLE(1'b0),
        .GS_LPDDR_TEX(1'b1), .TEX_VRAM_BASE(TEX_VRAM_BASE), .TEX_CACHE_BYTES(TEX_BYTES)
    ) 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),
        .gs_tex_rd_en_o(gs_tex_rd_en_o), .gs_tex_rd_addr_o(gs_tex_rd_addr_o),
        .tex_cache_data_i(tex_cache_data), .tex_cache_ready_i(tex_cache_ready),
        .tex_cache_hits_o(tex_cache_hits), .tex_bram_hits_o(tex_bram_hits)
    );

    gs_texture_cache #(
        .LPDDR_TEX_BASE(LPDDR_TEX_BASE), .TEX_VRAM_BASE(TEX_VRAM_BASE),
        .TEX_BYTES(TEX_BYTES), .N_BEATS(N_BEATS)
    ) u_cache (
        .axi_clk(clk), .axi_rst_n(rst_n),
        .fill_start(fill_start), .fill_done(fill_done),
        .fill_beats(fill_beats), .fill_bytes(fill_bytes), .rd_errs(tex_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(clk), .tex_rd_en(gs_tex_rd_en_o),
        .tex_rd_addr(gs_tex_rd_addr_o), .tex_rd_data(tex_cache_data), .tex_ready(tex_cache_ready)
    );

    // tritex texel word (mirrors bake.py tex_demo_texel): ABGR, A=FF.
    function automatic logic [31:0] texw(input int x, input int y);
        logic [7:0] rr, gg, bb; logic lft, top;
        lft = (x<4); top = (y<4);
        if      (top &&  lft) begin rr=8'hFF; gg=8'h00; bb=8'h00; end
        else if (top && !lft) begin rr=8'h00; gg=8'hFF; bb=8'h00; end
        else if (!top && lft) begin rr=8'h00; gg=8'h00; bb=8'hFF; end
        else                  begin rr=8'hFF; gg=8'hFF; bb=8'h00; end
        texw = {8'hFF, bb, gg, rr};
    endfunction

    // ---- behavioral LPDDR model: 512 words, sparse texel layout ----
    // texel(u,v) lives at word (v*64+u) (= VRAM byte (base+(v*64+u)*4) >> 2).
    logic [31:0] lpddr_mem [0:TEX_WORDS-1];
    initial begin
        for (int i=0; i<TEX_WORDS; i++) lpddr_mem[i] = 32'h0;
        for (int v=0; v<8; v++)
            for (int u=0; u<8; u++)
                lpddr_mem[v*64 + u] = texw(u, v);
    end

    // single-beat (arlen=0) read model, a few cycles latency.
    typedef enum logic [1:0] { S_IDLE, S_WAIT, S_DATA } sstate_t; sstate_t sst;
    logic [3:0] dly; int beat_idx;
    always_ff @(posedge clk) begin
        if (!rst_n) begin
            sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rresp<=2'b00; rdata<='0; dly<='0;
        end else begin
            arready<=0; rvalid<=0; rlast<=0;
            case (sst)
                S_IDLE: if (arvalid) begin
                    arready<=1; beat_idx<=(araddr-LPDDR_TEX_BASE)>>5; 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] <= lpddr_mem[beat_idx*8 + w];
                    rresp<=2'b00; rvalid<=1; rlast<=1; sst<=S_IDLE;
                end
            endcase
        end
    end

    // ----- reference: triangle interior + expected texel (UV==XY trick) -----
    localparam int VX0=1, VY0=1, VX1=7, VY1=1, VX2=2, VY2=6;
    int sx1, sy1, sx2, sy2;
    function automatic int edge_f(input int px,py,ax,ay,bx,by);
        return (px-ax)*(by-ay) - (py-ay)*(bx-ax); endfunction
    function automatic bit tolr(input int ax,ay,bx,by);
        int dx,dy; dx=bx-ax; dy=by-ay; return (dy>0)||((dy==0)&&(dx>0)); endfunction
    task automatic setup_ref(); int sa;
        sa=(VX1-VX0)*(VY2-VY0)-(VY1-VY0)*(VX2-VX0);
        if (sa<0) begin sx1=VX2; sy1=VY2; sx2=VX1; sy2=VY1; end
        else      begin sx1=VX1; sy1=VY1; sx2=VX2; sy2=VY2; end endtask
    function automatic bit ref_inside(input int px,py); int e0,e1,e2,b0,b1,b2;
        e0=edge_f(px,py,VX0,VY0,sx1,sy1); e1=edge_f(px,py,sx1,sy1,sx2,sy2);
        e2=edge_f(px,py,sx2,sy2,VX0,VY0);
        b0=tolr(VX0,VY0,sx1,sy1)?0:1; b1=tolr(sx1,sy1,sx2,sy2)?0:1; b2=tolr(sx2,sy2,VX0,VY0)?0:1;
        return ((e0+b0)<=0)&&((e1+b1)<=0)&&((e2+b2)<=0); endfunction
    function automatic logic [7:0] tex_exp_r(input int x,y); return ((x<4)==(y<4))?8'hFF:8'h00; endfunction
    function automatic logic [7:0] tex_exp_g(input int x,y); return (x>=4)?8'hFF:8'h00; endfunction
    function automatic logic [7:0] tex_exp_b(input int x,y); return ((x<4)&&(y>=4))?8'hFF:8'h00; endfunction

    // ----- frame capture (BRAM sync-read: index by delayed counters) -----
    logic [7:0] cap_r[0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic [7:0] cap_g[0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic [7:0] cap_b[0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic       cap_de[0:V_ACTIVE-1][0:H_ACTIVE-1];
    bit         capture_armed;
    logic [31:0] hcnt_d, vcnt_d;
    initial begin
        for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++) begin
            cap_r[y][x]=0; cap_g[y][x]=0; cap_b[y][x]=0; cap_de[y][x]=0; end
        capture_armed=0;
    end
    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) begin
        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; cap_de[vcnt_d][hcnt_d]<=1'b1;
        end
    end

    int errors, tex_pixels_ok, distinct_seen;
    bit ready_before_raster;

    initial begin
        errors=0; tex_pixels_ok=0; distinct_seen=0; ready_before_raster=0;
        setup_ref();
        rst_n=0; core_go=0; fill_start=0;
        repeat (4) @(posedge clk); rst_n=1; repeat (4) @(posedge clk);

        // warm the cache FIRST, independent of the VRAM upload. fill_start is an EDGE/TOGGLE:
        // flip it ONCE (and hold) to trigger a single fill — do NOT pulse 0->1->0, since the
        // falling edge would trigger a second fill that races the ready check below.
        @(posedge clk) fill_start<=1'b1;
        begin int gd=0; while (!fill_done && gd<5000) begin @(posedge clk); gd++; end end
        if (!fill_done)            begin $error("cache fill_done never asserted"); errors++; end
        if (fill_beats!==N_BEATS)  begin $error("fill_beats=%0d exp %0d", fill_beats, N_BEATS); errors++; end
        if (fill_bytes!==TEX_BYTES)begin $error("fill_bytes=%0d exp %0d", fill_bytes, TEX_BYTES); errors++; end
        if (tex_rd_errs!==0)       begin $error("tex_rd_errs=%0d exp 0", tex_rd_errs); errors++; end

        // now start the EE bootlet (uploads texture to VRAM, then draws).
        @(negedge clk); core_go=1'b1; @(negedge clk); core_go=1'b0;

        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);

        // STRONG PROOF: corrupt the VRAM texture region now (upload done, before
        // the raster reads texels). The cache holds the good texture from LPDDR;
        // a correct render hereafter proves texels came from the cache, not BRAM.
        ready_before_raster = tex_cache_ready;     // must already be warm
        if (!ready_before_raster) begin $error("cache not ready before raster"); errors++; end
        for (int wi=0; wi<TEX_WORDS; wi++)
            dut.u_vram.mem[(TEX_VRAM_BASE>>2) + wi] = 32'hDEAD_BEEF;

        if (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;
        @(posedge dut.u_pcrtc.end_of_frame); @(posedge clk); capture_armed=0;

        for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++)
            if (ref_inside(x,y)) begin
                logic [7:0] er,eg,eb; er=tex_exp_r(x,y); eg=tex_exp_g(x,y); eb=tex_exp_b(x,y);
                if (!cap_de[y][x]) begin $error("(%0d,%0d) DE never asserted", x,y); errors++; end
                else if (cap_r[y][x]!==er || cap_g[y][x]!==eg || cap_b[y][x]!==eb) begin
                    $error("[lpddrtex] (%0d,%0d) got (%02x,%02x,%02x) exp (%02x,%02x,%02x)",
                           x,y,cap_r[y][x],cap_g[y][x],cap_b[y][x],er,eg,eb); errors++;
                end else tex_pixels_ok++;
            end

        begin logic [23:0] first; bit have; logic [23:0] px;
            have=0; distinct_seen=1;
            for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++)
                if (ref_inside(x,y) && cap_de[y][x]) begin
                    px={cap_b[y][x],cap_g[y][x],cap_r[y][x]};
                    if (!have) begin first=px; have=1; end
                    else if (px!==first) distinct_seen=2;
                end
        end
        // Ch322 — the texel-source counter must show the cache served the triangle's
        // texels (and VRAM was corrupted, so BRAM fallback would have produced garbage).
        if (tex_cache_hits == 32'd0) begin $error("tex_cache_hits=0 — cache served no texels"); errors++; end
        $display("[lpddrtex] tex_cache_hits=%0d tex_bram_hits=%0d", tex_cache_hits, tex_bram_hits);
        if (distinct_seen<2) begin $error("textured triangle uniform — texturing collapsed"); errors++; end
        if (tex_pixels_ok<8) begin $error("too few interior textured px matched (%0d)", tex_pixels_ok); errors++; end
        if (!core_halt)      begin $error("core_halt low at end"); errors++; end
        if (!dma_done_seen)  begin $error("dma_done_seen never latched"); errors++; end
        if (raster_overflow) begin $error("raster_overflow set"); errors++; end

        $display("[lpddrtex] fill_done=%0d beats=%0d bytes=%0d rd_errs=%0d ready_pre_raster=%0d tex_ok=%0d distinct=%0d errors=%0d",
                 fill_done, fill_beats, fill_bytes, tex_rd_errs, ready_before_raster, tex_pixels_ok, distinct_seen, errors);
        if (errors==0) $display("[tb_top_psmct32_raster_demo_bram_lpddrtex] PASS");
        else           $display("[tb_top_psmct32_raster_demo_bram_lpddrtex] FAIL");
        $finish;
    end

    initial begin #20000000; $error("[tb_top_psmct32_raster_demo_bram_lpddrtex] TIMEOUT"); $finish; end
endmodule : tb_top_psmct32_raster_demo_bram_lpddrtex
