// ============================================================================
// tb_gs_lpddr_scanout_concurrency.sv  (Ch326 — reproduce the LPDDR-only-FB blank)
//
// The Ch326 board failure: with FB_LPDDR_ONLY the LPDDR scanout (rd_arb s0) is forced
// ON always, so for the FIRST time it shares the EMIF read channel CONCURRENTLY with
// the tile reload (s3, during render) and the HPS read-probe (s1, during the sweep).
// HDMI went blank + the probe sweep hung. tb_gs_tile_spill_grid8x8_lpddr only checks FB
// CONTENTS, never the scanout DISPLAY under concurrency — this TB fills that gap.
//
// Wires gs_lpddr_scanout (s0) + gs_tile_reload (s3) + gs_lpddr_rd_probe (s1) through the
// REAL gs_lpddr_rd_arb to ONE behavioral EMIF preloaded with a known 128x128 PSMCT32 FB.
// Then: (1) sweep a full frame on the scanout while (2) HAMMERING reload re-fills and
// (3) probe reads CONCURRENTLY, and assert the scanout's decoded r/g/b == the FB pixel
// for every pixel, the probe reads return the right word, and nothing hangs.
// ============================================================================
`timescale 1ns/1ps

module tb_gs_lpddr_scanout_concurrency;
    localparam [29:0] FB_BASE = 30'd0;            // FB at LPDDR 0 (this TB's mem)
    localparam int    CB      = 2048;             // 128x128 PSMCT32 = 64 KiB
    localparam int    FBW     = 128, FBH = 128;

    logic axi_clk=0, video_clk=0, rst_n=0;
    always #2  axi_clk   = ~axi_clk;              // ~250 MHz emif
    always #10 video_clk = ~video_clk;            // 50 MHz

    // ---- known FB: pixel p = ABGR {0xFF, B=p^0xAA, G=p^0x55, R=p} ----
    function automatic [31:0] pix32(input [31:0] p); pix32 = {8'hFF,(p[7:0]^8'hAA),(p[7:0]^8'h55),p[7:0]}; endfunction
    logic [255:0] mem [0:8191];
    initial begin
        for (int i=0;i<8192;i++) mem[i]='0;
        for (int gy=0; gy<FBH; gy++) for (int gx=0; gx<FBW; gx++) begin
            logic [29:0] a; a = FB_BASE + 30'((gy*FBW+gx)*4);   // color
            mem[a>>5][((a>>2)&3'd7)*32 +: 32] = pix32(gy*FBW+gx);
            a = 30'h0010_0000 + 30'((gy*FBW+gx)*4);             // Z region (for reload), distinct
            mem[a>>5][((a>>2)&3'd7)*32 +: 32] = 32'h0000_8000;
        end
    end

    // ---- s0: scanout (frame-cache, PSMCT32) ----
    wire [29:0] s0_aa; wire [1:0] s0_ab; wire [6:0] s0_id; wire [7:0] s0_al; wire [2:0] s0_as; wire s0_av,s0_ar;
    wire [255:0] s0_rd; wire [1:0] s0_rr; wire s0_rl,s0_rv,s0_rrdy;
    logic [31:0] vram_read_addr; logic frame_start; wire [7:0] sc_r,sc_g,sc_b; wire sc_valid;
    gs_lpddr_scanout #(.FB_BASE(FB_BASE), .VRAM_BASE(30'd0), .PSMCT32(1'b1), .CACHE_BEATS(CB)) u_scan (
        .axi_clk(axi_clk), .axi_rst_n(rst_n), .enable(1'b1), .frame_start(frame_start),
        .video_clk(video_clk), .vram_read_addr(vram_read_addr), .r(sc_r), .g(sc_g), .b(sc_b),
        .cache_valid(sc_valid), .rd_beats(), .rd_errs(),
        .araddr(s0_aa), .arburst(s0_ab), .arid(s0_id), .arlen(s0_al), .arsize(s0_as), .arvalid(s0_av), .arready(s0_ar),
        .rdata(s0_rd), .rresp(s0_rr), .rlast(s0_rl), .rvalid(s0_rv), .rready(s0_rrdy)
    );

    // ---- s3: tile reload (concurrent re-fills) ----
    wire [29:0] s3_aa; wire [1:0] s3_ab; wire [6:0] s3_id; wire [7:0] s3_al; wire [2:0] s3_as; wire s3_av,s3_ar;
    wire [255:0] s3_rd; wire [1:0] s3_rr; wire s3_rl,s3_rv,s3_rrdy;
    logic reload_start; logic [7:0] rl_raddr; wire [31:0] rl_color,rl_z; wire rl_ready;
    gs_tile_reload #(.COLOR_BASE(FB_BASE), .Z_BASE(30'h0010_0000), .TILE_W(16), .TILE_H(16),
                     .STRIDE_BYTES(512), .ROW_BEATS(2), .COLOR_W(32)) u_reload (
        .axi_clk(axi_clk), .axi_rst_n(rst_n), .reload_start(reload_start), .reload_base(30'd0), .reload_done(),
        .color_beats(), .z_beats(), .rd_errs(),
        .araddr(s3_aa), .arburst(s3_ab), .arid(s3_id), .arlen(s3_al), .arsize(s3_as), .arvalid(s3_av), .arready(s3_ar),
        .rdata(s3_rd), .rresp(s3_rr), .rlast(s3_rl), .rvalid(s3_rv), .rready(s3_rrdy),
        .serve_clk(video_clk), .raddr(rl_raddr), .color_o(rl_color), .z_o(rl_z), .reload_ready(rl_ready)
    );

    // ---- s1: HPS read-probe (concurrent reads) ----
    wire [29:0] s1_aa; wire [1:0] s1_ab; wire [6:0] s1_id; wire [7:0] s1_al; wire [2:0] s1_as; wire s1_av,s1_ar;
    wire [255:0] s1_rd; wire [1:0] s1_rr; wire s1_rl,s1_rv,s1_rrdy;
    logic pr_pulse; logic [31:0] pr_addr; wire pr_done; wire [31:0] pr_data;
    gs_lpddr_rd_probe #(.ADDR_W(30)) u_probe (
        .axi_clk(axi_clk), .axi_rst_n(rst_n), .rd_pulse(pr_pulse), .rd_addr(pr_addr),
        .rd_done(pr_done), .rd_data(pr_data), .rd_busy(),
        .araddr(s1_aa), .arburst(s1_ab), .arid(s1_id), .arlen(s1_al), .arsize(s1_as), .arvalid(s1_av), .arready(s1_ar),
        .rdata(s1_rd), .rresp(s1_rr), .rlast(s1_rl), .rvalid(s1_rv), .rready(s1_rrdy)
    );

    // ---- rd_arb (s0 scanout > s3 reload > s1 probe > s2 idle) -> behavioral EMIF ----
    wire [29:0] m_aa; wire [1:0] m_ab; wire [6:0] m_id; wire [7:0] m_al; wire [2:0] m_as; wire m_av,m_ar;
    wire [255:0] m_rd; wire [1:0] m_rr; wire m_rl,m_rv,m_rrdy;
    gs_lpddr_rd_arb u_arb (
        .clk(axi_clk), .rst_n(rst_n),
        .s0_araddr(s0_aa),.s0_arburst(s0_ab),.s0_arid(s0_id),.s0_arlen(s0_al),.s0_arsize(s0_as),.s0_arvalid(s0_av),.s0_arready(s0_ar),
        .s0_rdata(s0_rd),.s0_rresp(s0_rr),.s0_rlast(s0_rl),.s0_rvalid(s0_rv),.s0_rready(s0_rrdy),
        .s1_araddr(s1_aa),.s1_arburst(s1_ab),.s1_arid(s1_id),.s1_arlen(s1_al),.s1_arsize(s1_as),.s1_arvalid(s1_av),.s1_arready(s1_ar),
        .s1_rdata(s1_rd),.s1_rresp(s1_rr),.s1_rlast(s1_rl),.s1_rvalid(s1_rv),.s1_rready(s1_rrdy),
        .s2_araddr(30'd0),.s2_arburst(2'b01),.s2_arid(7'd4),.s2_arlen(8'd0),.s2_arsize(3'b101),.s2_arvalid(1'b0),.s2_arready(),
        .s2_rdata(),.s2_rresp(),.s2_rlast(),.s2_rvalid(),.s2_rready(1'b1),
        .s3_araddr(s3_aa),.s3_arburst(s3_ab),.s3_arid(s3_id),.s3_arlen(s3_al),.s3_arsize(s3_as),.s3_arvalid(s3_av),.s3_arready(s3_ar),
        .s3_rdata(s3_rd),.s3_rresp(s3_rr),.s3_rlast(s3_rl),.s3_rvalid(s3_rv),.s3_rready(s3_rrdy),
        .m_araddr(m_aa),.m_arburst(m_ab),.m_arid(m_id),.m_arlen(m_al),.m_arsize(m_as),.m_arvalid(m_av),.m_arready(m_ar),
        .m_rdata(m_rd),.m_rresp(m_rr),.m_rlast(m_rl),.m_rvalid(m_rv),.m_rready(m_rrdy)
    );
    // behavioral EMIF read slave (few-cycle latency)
    typedef enum logic [1:0] {RA,RL,RV} rs_t; rs_t rs; logic [29:0] rbeat; logic [15:0] rlat;
    assign m_ar=(rs==RA); assign m_rv=(rs==RV); assign m_rl=(rs==RV); assign m_rr=2'b00; assign m_rd=mem[rbeat[12:0]];
    always_ff @(posedge axi_clk or negedge rst_n) begin
        if (!rst_n) begin rs<=RA; rlat<=0; end
        else case (rs)
            RA: if (m_av) begin rbeat<=m_aa[29:5]; rlat<=(m_aa[8:5]==4'd0)?16'd3000:16'd8; rs<=RL; end
            RL: if (rlat==0) rs<=RV; else rlat<=rlat-1'b1;
            RV: if (m_rrdy) rs<=RA;
        endcase
    end

    function automatic [31:0] cref(input int gx, input int gy); cref = pix32(gy*FBW+gx); endfunction
    int errors;
    task automatic chk(input bit c, input string m); if (!c) begin errors++; $display("[conc] FAIL: %s", m); end endtask

    // concurrent reload hammer (s3) + probe hammer (s1) — keep the arb busy with all 3
    initial begin
        reload_start=0; rl_raddr=0;
        wait(rst_n); repeat(20) @(posedge axi_clk);
        forever begin
            @(posedge video_clk) reload_start<=1'b1; @(posedge video_clk) reload_start<=1'b0;
            repeat(2000) @(posedge axi_clk);   // re-arm reload periodically (concurrent with scanout)
        end
    end
    initial begin
        pr_pulse=0; pr_addr=0;
        wait(rst_n); repeat(30) @(posedge axi_clk);
        forever begin
            pr_addr <= 32'h0000_0040;
            @(posedge axi_clk) pr_pulse<=1'b1; @(posedge axi_clk) pr_pulse<=1'b0;
            begin int g=0; while(!pr_done && g<6000000) begin @(posedge axi_clk); g++; end end
            chk(pr_done, "probe read HUNG under concurrency (rd_arb starvation/deadlock)");
            chk(pr_data===cref(16,0), $sformatf("probe read corrupted: 0x%08x exp 0x%08x", pr_data, cref(16,0)));
            repeat(500) @(posedge axi_clk);
        end
    end

    int checked;
    initial begin
        errors=0; frame_start=0; vram_read_addr=0;
        repeat(8) @(posedge axi_clk); rst_n=1; repeat(8) @(posedge video_clk);

        // run several frames; on each, fill then sweep + verify the scanout output == FB
        for (int f=0; f<3; f++) begin
            @(posedge video_clk) frame_start<=1'b1; @(posedge video_clk) frame_start<=1'b0;
            begin int g=0; while(!sc_valid && g<6000000) begin @(posedge axi_clk); g++; end end
            chk(sc_valid, $sformatf("frame %0d: scanout cache never filled (BLANK) under concurrency", f));
        end
        // final frame: sweep the whole 128x128 and compare every pixel
        @(posedge video_clk) frame_start<=1'b1; @(posedge video_clk) frame_start<=1'b0;
        begin int g=0; while(!sc_valid && g<6000000) begin @(posedge axi_clk); g++; end end
        chk(sc_valid, "final scanout cache never filled (BLANK)");
        checked=0;
        for (int gy=0; gy<FBH; gy++) for (int gx=0; gx<FBW; gx++) begin
            logic [7:0] er,eg,eb; logic [31:0] c;
            vram_read_addr = (gy*FBW+gx)*4;
            @(posedge video_clk); @(posedge video_clk);
            c = cref(gx,gy); er=c[7:0]; eg=c[15:8]; eb=c[23:16];
            if (sc_r!==er || sc_g!==eg || sc_b!==eb) begin
                if (errors<8) $display("[conc] PIXEL (%0d,%0d): got(%0h,%0h,%0h) exp(%0h,%0h,%0h)", gx,gy,sc_r,sc_g,sc_b,er,eg,eb);
                errors++;
            end
            checked++;
        end
        $display("[conc] checked=%0d pixels under scanout+reload+probe concurrency, errors=%0d", checked, errors);
        if (errors==0) $display("[tb_gs_lpddr_scanout_concurrency] PASS");
        else           $display("[tb_gs_lpddr_scanout_concurrency] FAIL (%0d)", errors);
        $finish;
    end
    initial begin #200000000; $display("[tb_gs_lpddr_scanout_concurrency] TIMEOUT (a hang = the bug reproduced)"); $display("[tb_gs_lpddr_scanout_concurrency] FAIL"); $finish; end
endmodule
