// retroDE_ps2 — tb_top_psmct32_feeder_shapes_demo (Ch332)
//
// SHAPE VOCABULARY: the feeder is no longer triangle-only smoke. A RECTANGLE (filled quad) is
// expressed as two textured triangles sharing a diagonal — no new feeder record type, just two
// triangle records — proving the host can command quads on the existing Ch330/Ch331 path. Three
// runtime-switched scenes (all <= FIFO_DEPTH), no rebuild/reset:
//   TRI   : 3 half-tile triangles      tiles {0,5,10}
//   RECT  : 3 filled quads (6 prims)   tiles {0,5,10}   — same tiles, visibly FULLER than TRI
//   MIXED : triangles {0,15} + rects {5,10}             — both shapes in one scene
//
// PROOF: empty tiles stay clean; a rectangle tile is filled MORE than a triangle tile (the quad's
// two triangles tile cleanly with no diagonal seam); records_emitted == prim count per scene.

`timescale 1ns/1ps

module tb_top_psmct32_feeder_shapes_demo;

    localparam int H_ACTIVE = 64, V_ACTIVE = 64;
    localparam real MARG = 0.12;
    localparam logic [1:0] C_RUN = 2'd1, C_READY = 2'd3;

    logic clk, rst_n;
    initial clk = 1'b0;
    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, feeder_go_tb;

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE(H_ACTIVE), .V_ACTIVE(V_ACTIVE),
        .VRAM_BYTES(32*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(4), .TILE_FIFO_DEPTH(8),
        .BIN_BUFFER_ENABLE(1'b1), .HEARTBEAT_SPLICE_ENABLE(1'b0),
        .FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(256)
    ) 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),
        .feeder_stg_we_i(1'b0), .feeder_stg_waddr_i(12'd0), .feeder_stg_wdata_i(64'd0),
        .feeder_go_i(feeder_go_tb),
        .feeder_ready_o(), .feeder_records_o(), .feeder_waits_o()
    );

    logic [7:0] cap_r[0:V_ACTIVE-1][0:H_ACTIVE-1], cap_g[0:V_ACTIVE-1][0:H_ACTIVE-1], cap_b[0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic       cap_de[0:V_ACTIVE-1][0:H_ACTIVE-1];
    bit capture_armed;
    task automatic clear_cap;
        for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++) begin
            cap_r[y][x]=0; cap_g[y][x]=0; cap_b[y][x]=0; cap_de[y][x]=0; end
    endtask
    initial begin clear_cap(); capture_armed=1'b0; end
    logic [31:0] hcnt_d, vcnt_d;
    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)
        if (rst_n && capture_armed && de && (vcnt_d<V_ACTIVE) && (hcnt_d<H_ACTIVE)) begin
            cap_r[vcnt_d][hcnt_d]<=r; cap_g[vcnt_d][hcnt_d]<=g; cap_b[vcnt_d][hcnt_d]<=b; cap_de[vcnt_d][hcnt_d]<=1'b1; end

    function automatic int blue_in_box(input int bx0, input int by0);
        int n; begin n=0;
            for (int y=by0;y<by0+16;y++) for (int x=bx0;x<bx0+16;x++)
                if (cap_de[y][x] && cap_b[y][x]===8'hFF && cap_r[y][x]===8'h00 && cap_g[y][x]===8'h00) n++;
            blue_in_box = n;
        end
    endfunction

    task automatic capture_frame;
        clear_cap();
        @(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;
    endtask
    task automatic stage_and_run(input string memfile);
        $readmemh(memfile, dut.g_feeder.feeder_stg);
        @(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
        wait (dut.g_feeder.cst == C_RUN); wait (dut.g_feeder.cst == C_READY);
        capture_frame();
    endtask

    int errors;
    // tri fill (half a 13x13 tile ~80px) vs rect fill (full 13x13 ~150-169px). Bands with a clear gap.
    localparam int TRI_MIN = 30, TRI_MAX = 120, RECT_MIN = 130;

    // kinds: 0=empty (must be clean), 1=triangle (TRI band), 2=rectangle (RECT band)
    task automatic check_shapes(input string label, input logic [31:0] kinds2b, input int exp_records);
        int nb, kind;
        for (int t=0;t<16;t++) begin
            nb   = blue_in_box((t%4)*16, (t/4)*16);
            kind = (kinds2b >> (2*t)) & 2'b11;
            case (kind)
                0: if (nb != 0)                          begin $error("[%s] tile %0d empty but %0d blue", label,t,nb); errors++; end
                1: if (nb < TRI_MIN || nb > TRI_MAX)     begin $error("[%s] tile %0d TRI but %0d blue (want %0d..%0d)", label,t,nb,TRI_MIN,TRI_MAX); errors++; end
                2: if (nb < RECT_MIN)                    begin $error("[%s] tile %0d RECT but only %0d blue (want >=%0d) — quad not filled / seam?", label,t,nb,RECT_MIN); errors++; end
            endcase
        end
        if (int'(dut.feeder_records) != exp_records) begin $error("[%s] records=%0d (want %0d)", label, int'(dut.feeder_records), exp_records); errors++; end
        $display("[%s] t0=%0d t5=%0d t10=%0d t15=%0d records=%0d",
                 label, blue_in_box(0,0), blue_in_box(16,16), blue_in_box(32,32), blue_in_box(48,48), int'(dut.feeder_records));
    endtask

    initial begin
        errors=0; feeder_go_tb=1'b0;
        $readmemh(`FEEDER_SHAPE_TRI_FILE, dut.g_feeder.feeder_stg);   // power-up scene = TRI

        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);

        // TRI: triangles in 0/5/10  (kinds: t0=1,t5=1,t10=1)
        wait (dut.g_feeder.cst == C_READY); capture_frame();
        check_shapes("TRI  (tri 0/5/10)",      (32'd1<<(2*0))|(32'd1<<(2*5))|(32'd1<<(2*10)), 3);

        // RECT: filled quads in 0/5/10  (kinds: t0=2,t5=2,t10=2) — same tiles, fuller
        stage_and_run(`FEEDER_SHAPE_RECT_FILE);
        check_shapes("RECT (quad 0/5/10)",     (32'd2<<(2*0))|(32'd2<<(2*5))|(32'd2<<(2*10)), 6);

        // MIXED: tris in 0/15, rects in 5/10
        stage_and_run(`FEEDER_SHAPE_MIXED_FILE);
        check_shapes("MIXED (tri 0/15 rect 5/10)", (32'd1<<(2*0))|(32'd2<<(2*5))|(32'd2<<(2*10))|(32'd1<<(2*15)), 6);

        if (raster_overflow) begin $error("raster_overflow set"); errors++; end
        if (!core_halt)      begin $error("core_halt low"); errors++; end
        $display("[tb_top_psmct32_feeder_shapes_demo] errors=%0d", errors);
        if (errors==0) $display("[tb_top_psmct32_feeder_shapes_demo] PASS");
        else           $display("[tb_top_psmct32_feeder_shapes_demo] FAIL");
        $finish;
    end
    initial begin #1200000000; $error("[tb_top_psmct32_feeder_shapes_demo] TIMEOUT"); $finish; end
endmodule : tb_top_psmct32_feeder_shapes_demo
