// retroDE_ps2 — tb_gs_lpddr_fb_writer (Ch317)
//
// Focused proof of the LPDDR framebuffer write sink: drive a synthetic PSMCT16
// tile-flush pixel stream and verify
//   1. ADDRESS/DATA/STRIDE: every pixel lands at fb_base + (y*pitch + x)*2 with the
//      correct halfword in the byte-addressed backing FB.
//   2. BURST COALESCING: a tile's 16-pixel rows (contiguous +2) coalesce to ONE
//      burst per row (16 bursts for a 16x16 tile); a row-to-row +pitch jump breaks
//      the burst.
//   3. 4 KiB CAP: a long contiguous run splits at MAX_BURST_BYTES (the AXI4 rule).
//   4. COUNTERS: bytes_written == 2*pixels, no FIFO over/underflow.

`timescale 1ns/1ps

module tb_gs_lpddr_fb_writer;
    localparam int FB_BYTES = 16384;          // room for the 64-wide cases
    localparam int PITCH     = 128;           // 64-px PSMCT16 row = 128 bytes
    localparam int MAXB      = 4096;

    logic clk, rst_n; initial clk=0; always #5 clk=~clk;
    logic        enable, px_emit;
    logic [31:0] px_addr;
    logic [15:0] px_pix16;
    logic [31:0] bytes_written, burst_count, busy_cycles, fifo_overflow_count, fifo_underflow_count;
    logic [15:0] fifo_occ;

    gs_lpddr_fb_writer #(.FB_BYTES(FB_BYTES), .FIFO_DEPTH(32), .MAX_BURST_BYTES(MAXB)) dut (
        .clk(clk), .rst_n(rst_n), .enable(enable),
        .px_emit(px_emit), .px_addr(px_addr), .px_pix16(px_pix16),
        .bytes_written(bytes_written), .burst_count(burst_count), .busy_cycles(busy_cycles),
        .fifo_overflow_count(fifo_overflow_count), .fifo_underflow_count(fifo_underflow_count),
        .fifo_occ(fifo_occ)
    );

    function automatic logic [15:0] pat(input int x, input int y);
        pat = 16'((x*7 + y*131 + 1) & 16'hFFFF);   // distinct per (x,y), nonzero
    endfunction

    // contiguous-in-time emit (1 pixel/cycle, no gap — matches the real flush sweep,
    // so the staging FIFO stays fed and contiguous-address runs coalesce into bursts).
    task automatic emit1(input logic [31:0] a, input logic [15:0] p);
        @(negedge clk); px_emit=1'b1; px_addr=a; px_pix16=p;
    endtask
    task automatic emit_off(); @(negedge clk); px_emit=1'b0; endtask
    task automatic drain(); @(negedge clk); px_emit=1'b0; repeat(40) @(posedge clk); endtask

    int errors; initial errors=0;
    logic [15:0] got;
    int x,y;

    initial begin
        enable=0; px_emit=0; px_addr=0; px_pix16=0; rst_n=0;
        repeat(4) @(posedge clk); rst_n=1; repeat(2) @(posedge clk);
        enable=1; @(posedge clk);

        // ===== CASE A — one 16x16 tile at origin, tile-local row order =====
        for (y=0; y<16; y++)
            for (x=0; x<16; x++)
                emit1(32'(y*PITCH + x*2), pat(x,y));
        drain();
        // data/address proof
        for (y=0; y<16; y++)
            for (x=0; x<16; x++) begin
                got = {dut.fbmem[y*PITCH + x*2 + 1], dut.fbmem[y*PITCH + x*2]};
                if (got !== pat(x,y)) begin
                    if (errors<10) $error("[lpfb] A (%0d,%0d) addr=%0d got=%04x exp=%04x", x,y,y*PITCH+x*2,got,pat(x,y));
                    errors++;
                end
            end
        // 256 pixels, 16 contiguous rows -> 16 bursts; 512 bytes; no over/underflow
        if (bytes_written !== 32'd512)  begin $error("[lpfb] A bytes=%0d (want 512)", bytes_written); errors++; end
        if (burst_count  !== 32'd16)    begin $error("[lpfb] A bursts=%0d (want 16 = one per tile-row)", burst_count); errors++; end
        if (fifo_overflow_count !== 0)  begin $error("[lpfb] A overflow=%0d (want 0)", fifo_overflow_count); errors++; end
        if (fifo_underflow_count !== 0) begin $error("[lpfb] A underflow=%0d (want 0)", fifo_underflow_count); errors++; end
        $display("[lpfb] CASE A: 256px tile -> bytes=%0d bursts=%0d occ_max=%0d busy=%0d ovf=%0d",
                 bytes_written, burst_count, fifo_occ, busy_cycles, fifo_overflow_count);

        // ===== CASE B — long CONTIGUOUS run (2049 px) must split at 4 KiB =====
        // base well past case A; 2049 contiguous +2 addresses = 4098 bytes > 4096.
        begin
            int unsigned base; int b0; int b1;
            base = 32'd8192;
            b0 = int'(burst_count);
            for (x=0; x<2049; x++) emit1(32'(base + x*2), pat(x,0));
            drain();
            b1 = int'(burst_count);
            // 4098 bytes contiguous -> ceil(4098/4096) = 2 bursts (4096 + 2).
            if ((b1 - b0) !== 2) begin $error("[lpfb] B contiguous-run bursts=%0d (want 2 via 4KiB cap)", b1-b0); errors++; end
            // spot-check a few stored pixels
            for (x=0; x<2049; x+=337) begin
                got = {dut.fbmem[base + x*2 + 1], dut.fbmem[base + x*2]};
                if (got !== pat(x,0)) begin $error("[lpfb] B x=%0d got=%04x exp=%04x", x, got, pat(x,0)); errors++; end
            end
            $display("[lpfb] CASE B: 2049-px contiguous run -> +%0d bursts (4KiB cap), bytes=%0d", b1-b0, bytes_written);
        end

        // ===== CASE C — enable=0 is inert =====
        // (counters already advanced; just confirm a disabled write does nothing)
        begin int unsigned bw0; bw0 = bytes_written;
            enable=0; emit1(32'd4, 16'hBEEF); drain();
            if (bytes_written !== bw0) begin $error("[lpfb] C disabled write changed bytes (%0d->%0d)", bw0, bytes_written); errors++; end
            if (dut.fbmem[4] === 8'hEF) begin $error("[lpfb] C disabled write hit fbmem"); errors++; end
            $display("[lpfb] CASE C: enable=0 inert (bytes unchanged=%0d)", bytes_written);
        end

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