// ============================================================================
// tb_top_psmct32_tile_lpddr128_demo — Ch321
//
// Structural verification that the 128x128 PSMCT16 framebuffer renders: an 8x8
// grid of 16x16 tiles (64 tiles), every tile cleared (256 px) and flushed in
// PSMCT16, no raster overflow, 3 primitives, and the scanned-out frame is filled
// (not black). The exact pixel content is validated on the board (the scene is
// the 64x64 psmct16fb demo scaled 2x). Fixture: bios/payload_tile_lpddr128.
// ============================================================================
`timescale 1ns/1ps

module tb_top_psmct32_tile_lpddr128_demo;
    localparam int H_ACTIVE = 128, V_ACTIVE = 128;
    localparam int NTILES = 64;   // 8x8

    logic clk = 1'b0, rst_n;
    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(64*1024), .LPDDR_FB_BYTES(32*1024), .PSMCT32_SWIZZLE(1'b0),
        .COMBINED_TAZ(1'b1), .TILE_LOCAL(1'b1), .TILE_COLS(8), .TILE_ROWS(8),
        .TILE_MULTIPRIM(1'b1), .TILE_PRIM_COUNT(3), .BIN_BUFFER_ENABLE(1'b1),
        .TILE_COLOR_PSMCT16(1'b1)
    ) 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)
    );

    // ---- flush-stream counters ----
    int clear_cw [0:NTILES-1];
    int flush_emits, flush_psmct16, flush_psmct32, flush_be_bad;
    bit saw_prim_count_3;
    initial begin
        for (int t=0;t<NTILES;t++) clear_cw[t]=0;
        flush_emits=0; flush_psmct16=0; flush_psmct32=0; flush_be_bad=0; saw_prim_count_3=0;
    end
    always_ff @(posedge clk) if (rst_n) begin
        if (dut.u_gs.tile_phase==2'd1 /*TP_CLEAR*/ && dut.u_gs.tile_color_we) begin
            int t; t = int'(dut.u_gs.tile_row_r)*8 + int'(dut.u_gs.tile_col_r);
            if (t>=0 && t<NTILES) clear_cw[t] <= clear_cw[t] + 1;
        end
        if (dut.u_gs.raster_pixel_emit) begin
            flush_emits <= flush_emits + 1;
            if (dut.u_gs.raster_pixel_psm_q == 6'h02) flush_psmct16 <= flush_psmct16 + 1;
            if (dut.u_gs.raster_pixel_psm_q == 6'h00) flush_psmct32 <= flush_psmct32 + 1;
            if (dut.u_gs.raster_pixel_be_q  != 4'b0011) flush_be_bad <= flush_be_bad + 1;
        end
        if (dut.u_gs.raster_active && (int'(dut.u_gs.prim_count_r)==3)) saw_prim_count_3 <= 1'b1;
    end

    // ---- coarse content capture (one scanned-out frame) ----
    int n_de, n_nonblack, n_distinct_from_bg;
    logic [23:0] bg_seen;  // first non-black pixel = the clear/bg color
    bit bg_set;
    bit capture_armed;
    logic [31:0] hcnt_d, vcnt_d;
    initial begin n_de=0; n_nonblack=0; n_distinct_from_bg=0; bg_set=0; bg_seen=0; 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
            n_de <= n_de + 1;
            if ({r,g,b} != 24'd0) begin
                n_nonblack <= n_nonblack + 1;
                if (!bg_set) begin bg_seen <= {r,g,b}; bg_set <= 1'b1; end
                else if ({r,g,b} != bg_seen) n_distinct_from_bg <= n_distinct_from_bg + 1;
            end
        end
    end

    int errors;
    initial begin
        errors=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);
        @(negedge dut.u_gs.raster_active);
        repeat(10) @(posedge clk);
        // capture exactly one scanned-out frame
        @(posedge dut.u_pcrtc.end_of_frame); @(posedge clk); capture_armed=1'b1;
        @(posedge dut.u_pcrtc.end_of_frame); @(posedge clk); capture_armed=1'b0;

        // ---- structural proof: every tile cleared + flushed, all PSMCT16 ----
        for (int t=0;t<NTILES;t++)
            if (clear_cw[t] != 256) begin $error("[lp128] tile %0d cleared %0d (want 256)", t, clear_cw[t]); errors++; end
        if (flush_emits   != NTILES*256) begin $error("[lp128] flush emitted %0d (want %0d)", flush_emits, NTILES*256); errors++; end
        if (flush_psmct16 != flush_emits) begin $error("[lp128] PSMCT16 flushes %0d != emits %0d", flush_psmct16, flush_emits); errors++; end
        if (flush_psmct32 != 0)           begin $error("[lp128] %0d PSMCT32 flushes leaked", flush_psmct32); errors++; end
        if (flush_be_bad  != 0)           begin $error("[lp128] %0d flushes with be!=0011", flush_be_bad); errors++; end
        if (!saw_prim_count_3)            begin $error("[lp128] prim_count_r never == 3"); errors++; end
        if (raster_overflow)              begin $error("[lp128] raster_overflow asserted at 8x8/3-prim"); errors++; end
        if (!frame_seen)                  begin $error("[lp128] frame_seen never asserted"); errors++; end

        // ---- coarse content: frame is filled (not black) + has the triangles ----
        if (n_de        < 16000) begin $error("[lp128] only %0d active px captured (want ~16384)", n_de); errors++; end
        if (n_nonblack  < 15000) begin $error("[lp128] only %0d non-black px (frame not filled)", n_nonblack); errors++; end
        if (n_distinct_from_bg < 100) begin $error("[lp128] only %0d px differ from bg (triangles missing)", n_distinct_from_bg); errors++; end

        $display("[lp128] tiles=64 flush_emits=%0d psmct16=%0d ovf=%0d | de=%0d nonblack=%0d tri=%0d errors=%0d",
                 flush_emits, flush_psmct16, raster_overflow, n_de, n_nonblack, n_distinct_from_bg, errors);
        if (errors==0) $display("[tb_top_psmct32_tile_lpddr128_demo] PASS");
        else           $display("[tb_top_psmct32_tile_lpddr128_demo] FAIL");
        $finish;
    end

    initial begin
        #50_000_000;
        $display("[tb_top_psmct32_tile_lpddr128_demo] TIMEOUT");
        $display("[tb_top_psmct32_tile_lpddr128_demo] FAIL");
        $finish;
    end
endmodule
