// retroDE_ps2 — tb_top_psmct32_tile_lpddrfb_demo (Ch317)
//
// LPDDR-backed framebuffer, TILE-FLUSH ONLY. Runs the Ch313 full-PSMCT16-FB scene
// (4x4 grid, 64x64, payload_tile_psmct16fb) with LPDDR_FB_ENABLE=1, so the PSMCT16
// tile FLUSH is ALSO committed to the gs_lpddr_fb_writer model (linear address,
// per-row bursts, 4 KiB cap). Tile color/Z + texture stay on-chip.
//
// PROOF (write/readback): the LPDDR framebuffer must hold EXACTLY what the on-chip
// BRAM framebuffer holds — every one of the 64*64 pixels read back from the LPDDR
// model (pix16 = {fbmem[a+1],fbmem[a]}, a=(y*64+x)*2) equals the BRAM FB's pix16 at
// the same screen pixel. (The BRAM FB itself was proven against the rendered scene
// in Ch313 via scanout, so LPDDR==BRAM closes the loop.) Plus the scene is real
// (clear-green + the three prim colours), and the bandwidth/over-underflow counters
// are sane: 4096 px -> 8192 bytes, one 32-byte burst per tile-row (256), no overflow.

`timescale 1ns/1ps

module tb_top_psmct32_tile_lpddrfb_demo;

    localparam int H_ACTIVE = 64, V_ACTIVE = 64;

    logic clk, rst_n; initial clk=0; 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;

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE(H_ACTIVE), .V_ACTIVE(V_ACTIVE),
        .VRAM_BYTES(16*1024), .PSMCT32_SWIZZLE(1'b0),
        .COMBINED_TAZ(1'b1), .TILE_LOCAL(1'b1), .TILE_COLS(4), .TILE_ROWS(4),
        .TILE_MULTIPRIM(1'b1), .TILE_PRIM_COUNT(3), .BIN_BUFFER_ENABLE(1'b1),
        .TILE_COLOR_PSMCT16(1'b1),
        .LPDDR_FB_ENABLE(1'b1), .LPDDR_FB_BYTES(8192)
    ) 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)
    );

    // BRAM FB pix16 for screen (x,y): word = (y*64+x)>>1, halfword per bit0.
    function automatic logic [15:0] bram_pix16(input int x, input int y);
        int idx; logic [31:0] w;
        begin
            idx = y*64 + x;
            w   = dut.u_vram.mem[idx >> 1];
            bram_pix16 = idx[0] ? w[31:16] : w[15:0];
        end
    endfunction
    // LPDDR FB pix16 for screen (x,y): byte addr = (y*64+x)*2.
    function automatic logic [15:0] lpddr_pix16(input int x, input int y);
        int a; begin a = (y*64 + x)*2; lpddr_pix16 = {dut.g_lpddr_fb.u_lpddr_fb.fbmem[a+1], dut.g_lpddr_fb.u_lpddr_fb.fbmem[a]}; end
    endfunction

    int errors, n_eq, n_clear, n_nonclear;
    logic [15:0] clear16;
    int x,y;
    int bytes_w, bursts, busy, ovf, unf, occ;

    initial begin
        errors=0; n_eq=0; n_clear=0; n_nonclear=0;
        rst_n=1'b0; core_go=1'b0;
        repeat(4) @(posedge clk); rst_n=1'b1; repeat(8) @(posedge clk);
        @(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);
        @(posedge dut.u_gs.raster_active);
        repeat(120000) @(posedge clk);                 // full-render + LPDDR-drain settle

        bytes_w = int'(dut.lpfb_bytes_written);
        bursts  = int'(dut.lpfb_burst_count);
        busy    = int'(dut.lpfb_busy_cycles);
        ovf     = int'(dut.lpfb_overflow_count);
        unf     = int'(dut.lpfb_underflow_count);
        occ     = int'(dut.lpfb_occ);

        // the clear colour as a PSMCT16 word, read straight from the BRAM FB at a
        // pixel that no prim covers (top-right corner of the 64x64 frame).
        clear16 = bram_pix16(60, 4);

        // PROOF — LPDDR FB == BRAM FB for every pixel; tally clear vs non-clear.
        for (y=0; y<64; y++) begin
            for (x=0; x<64; x++) begin
                logic [15:0] lp, br;
                lp = lpddr_pix16(x,y); br = bram_pix16(x,y);
                if (lp === br) n_eq++;
                else begin
                    if (errors<12) $error("[lpfb] (%0d,%0d) LPDDR=%04x != BRAM=%04x", x,y,lp,br);
                    errors++;
                end
                if (br === clear16) n_clear++; else n_nonclear++;
            end
        end

        if (n_eq != 4096)        begin $error("[lpfb] LPDDR==BRAM only %0d/4096", n_eq); errors++; end
        if (n_nonclear < 50)     begin $error("[lpfb] only %0d non-clear px — scene didn't render to LPDDR", n_nonclear); errors++; end
        if (n_clear < 1000)      begin $error("[lpfb] only %0d clear px — FB looks wrong", n_clear); errors++; end
        // counters
        if (bytes_w != 8192)     begin $error("[lpfb] bytes_written=%0d (want 8192 = 4096 px x 2)", bytes_w); errors++; end
        if (ovf != 0)            begin $error("[lpfb] fifo_overflow=%0d (want 0)", ovf); errors++; end
        if (unf != 0)            begin $error("[lpfb] fifo_underflow=%0d (want 0)", unf); errors++; end
        if (bursts < 1)          begin $error("[lpfb] bursts=%0d (want >0)", bursts); errors++; end
        if (!core_halt)          begin $error("core_halt low"); errors++; end

        // effective GB/s for this small path (1 cycle = 10 ns => 100 MHz model clock)
        $display("[lpfb] bytes=%0d bursts=%0d (%.1f B/burst) busy=%0d occ_max=%0d ovf=%0d unf=%0d  eff=%.2f GB/s @100MHz",
                 bytes_w, bursts, (bursts>0)?(real'(bytes_w)/real'(bursts)):0.0, busy, occ, ovf, unf,
                 (busy>0) ? (real'(bytes_w) / (real'(busy)*10.0)) : 0.0);
        $display("[tb_top_psmct32_tile_lpddrfb_demo] LPDDR==BRAM=%0d/4096 clear=%0d nonclear=%0d clear16=%04x errors=%0d",
                 n_eq, n_clear, n_nonclear, clear16, errors);
        if (errors==0) $display("[tb_top_psmct32_tile_lpddrfb_demo] PASS");
        else           $display("[tb_top_psmct32_tile_lpddrfb_demo] FAIL");
        $finish;
    end
    initial begin #600000000; $error("[tb_top_psmct32_tile_lpddrfb_demo] TIMEOUT"); $finish; end
endmodule : tb_top_psmct32_tile_lpddrfb_demo
