// ============================================================================
// tb_gs_lpddr_scanout_psm32 — Ch324 Brick 3
//
// Proves gs_lpddr_scanout's PSMCT32 decode + the FB_BASE != VRAM_BASE split (the
// spill-profile config): the FB is FETCHED from LPDDR at FB_BASE=0x400000 but the
// PCRTC addresses it BRAM-relative (VRAM_BASE=0). A known 64x64 PSMCT32 frame
// (pixel p = ABGR {0xFF, p^0xAA, p^0x55, p}) is preloaded; we fill the cache, then
// present 0-based vram_read_addr values and check the decoded r/g/b == the ABGR
// channels, that the AR fetches came from FB_BASE+, and out-of-range -> black.
// ============================================================================
`timescale 1ns/1ps

module tb_gs_lpddr_scanout_psm32;
    localparam [29:0] FB_BASE = 30'h0040_0000;   // LPDDR fetch base (= COLOR_SPILL_BASE)
    localparam int    CB = 512;                  // 64x64 PSMCT32 = 16 KiB = 512 beats

    logic axi_clk = 0, video_clk = 0;
    always #2  axi_clk   = ~axi_clk;
    always #10 video_clk = ~video_clk;

    logic        axi_rst_n, enable, frame_start;
    logic [31:0] vram_read_addr;
    logic [7:0]  r, g, b;
    logic        cache_valid;
    logic [31:0] rd_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;

    gs_lpddr_scanout #(.FB_BASE(FB_BASE), .VRAM_BASE(30'd0), .PSMCT32(1'b1), .CACHE_BEATS(CB)) dut (
        .axi_clk(axi_clk), .axi_rst_n(axi_rst_n),
        .enable(enable), .frame_start(frame_start),
        .video_clk(video_clk), .vram_read_addr(vram_read_addr),
        .r(r), .g(g), .b(b),
        .cache_valid(cache_valid), .rd_beats(rd_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)
    );

    // known frame: pixel p (32-bit ABGR) = {A=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
    function automatic [255:0] beat_data(input [31:0] beat);
        integer i;
        for (i = 0; i < 8; i = i + 1) beat_data[32*i +: 32] = pix32(beat*8 + i);  // pixel index = beat*8+lane
    endfunction

    // AXI read-slave: serve beat = (araddr - FB_BASE) >> 5 (proves the fetch base offset).
    localparam [1:0] S_IDLE=0, S_LAT=1, S_RESP=2;
    logic [1:0] sst; logic [2:0] sdly; logic [29:0] sbeat;
    bit  fetch_below_base;
    always_ff @(posedge axi_clk or negedge axi_rst_n) begin
        if (!axi_rst_n) begin
            sst<=S_IDLE; arready<=0; rvalid<=0; rlast<=0; rdata<='0; rresp<=2'b00; sdly<=0; sbeat<=0; fetch_below_base<=0;
        end else begin
            arready <= 1'b0;
            case (sst)
                S_IDLE: if (arvalid) begin
                            if (araddr < FB_BASE) fetch_below_base <= 1'b1;     // must NEVER fetch below the FB base
                            arready <= 1'b1; sbeat <= (araddr - FB_BASE) >> 5; sdly <= 3'd3; sst <= S_LAT;
                        end
                S_LAT:  if (sdly==0) begin rdata<=beat_data(sbeat); rresp<=2'b00; rlast<=1'b1; rvalid<=1'b1; sst<=S_RESP; end
                        else sdly <= sdly-1;
                S_RESP: if (rready) begin rvalid<=0; rlast<=0; sst<=S_IDLE; end
                default: sst <= S_IDLE;
            endcase
        end
    end

    integer errors = 0;
    task automatic check_pixel(input [31:0] addr);
        logic [31:0] p; logic [7:0] er,eg,eb;
        begin
            vram_read_addr = addr;
            @(posedge video_clk); @(posedge video_clk);
            p = addr >> 2;                                     // VRAM_BASE=0 -> pixel index = addr/4
            er = p[7:0]; eg = p[7:0]^8'h55; eb = p[7:0]^8'hAA;
            if (r!==er || g!==eg || b!==eb) begin
                errors = errors + 1;
                $display("[psm32] MISMATCH @0x%0h (px %0d): got(%0h,%0h,%0h) exp(%0h,%0h,%0h)", addr, p, r, g, b, er, eg, eb);
            end
        end
    endtask
    task automatic check_black(input [31:0] addr);
        begin
            vram_read_addr = addr; @(posedge video_clk); @(posedge video_clk);
            if (r!==8'd0 || g!==8'd0 || b!==8'd0) begin
                errors = errors + 1; $display("[psm32] EXPECTED BLACK @0x%0h: got(%0h,%0h,%0h)", addr, r, g, b);
            end
        end
    endtask

    initial begin
        enable=0; frame_start=0; vram_read_addr=0;
        axi_rst_n=0; repeat(8) @(posedge axi_clk); axi_rst_n=1; repeat(4) @(posedge video_clk);

        check_black(32'h0000_0000);                              // before fill -> black

        enable = 1'b1;
        @(posedge video_clk); frame_start = 1'b1; @(posedge video_clk); frame_start = 1'b0;
        begin integer to; to=0;
            while (!cache_valid && to<200000) begin @(posedge axi_clk); to=to+1; end
            if (!cache_valid) begin errors=errors+1; $display("[psm32] cache_valid TIMEOUT"); end
        end
        if (rd_beats !== CB)    begin errors=errors+1; $display("[psm32] rd_beats=%0d exp %0d", rd_beats, CB); end
        if (rd_errs  !== 32'd0) begin errors=errors+1; $display("[psm32] rd_errs=%0d exp 0", rd_errs); end
        if (fetch_below_base)   begin errors=errors+1; $display("[psm32] FETCH below FB_BASE (offset split broken)"); end

        check_pixel(32'h0000_0000);   // px 0, beat 0 lane 0
        check_pixel(32'h0000_0004);   // px 1
        check_pixel(32'h0000_001C);   // px 7, beat 0 lane 7
        check_pixel(32'h0000_0020);   // px 8, beat 1 lane 0
        check_pixel(32'h0000_0100);   // px 64 (row 1, col 0)
        check_pixel(32'h0000_3FFC);   // px 4095, last in-range (16 KiB)
        check_black(32'h0000_4000);   // byte 16384 = first past the 64x64 PSMCT32 frame

        $display("[tb_gs_lpddr_scanout_psm32] errors=%0d", errors);
        if (errors==0) $display("[tb_gs_lpddr_scanout_psm32] PASS");
        else           $display("[tb_gs_lpddr_scanout_psm32] FAIL");
        $finish;
    end
    initial begin #800000; $display("[tb_gs_lpddr_scanout_psm32] TIMEOUT"); $display("[tb_gs_lpddr_scanout_psm32] FAIL"); $finish; end
endmodule
