// retroDE_ps2 — tb_top_psmct32_raster_demo_bram_clut4 (Ch297)
//
// TOP-LEVEL PSMT4 INDEXED-TEXTURE + CLUT demo for the BRAM BOARD VARIANT
// (the variant the de25_nano board top instantiates). Proves the 4-bit
// PALETTIZED texture path — the MOST common PS2 texture format — renders
// end-to-end through top_psmct32_raster_demo_bram exactly as a board load
// would, built directly on the Ch296 PSMT8+CLUT rung (only NEW datapath
// bit: the nibble extract in gs_texture_unit).
//
//   GIF payload (payload_clut4.mem) — NO new EE/BIOS scaffolding:
//     U1 BITBLT  -> uploads the 16-entry CLUT (PSMCT32 palette) to VRAM
//     U2 BITBLT  -> uploads an 8x8 PSMT4 texture (2 indices/byte)
//     U3 PRIM(SPRITE+TME) + TEX0(PSM=PSMT4, CLD=1 -> VRAM->CLUT load)
//                 + UV/XYZ2: a PSMT4-textured 8x8 sprite at (0,0)..(7,7)
//     U5 PACKED  -> a FLAT green control sprite at (8,0)..(15,7)
//
//   On TEX0 commit, clut_loader_stub copies the CLUT from VRAM into
//   clut_stub. During the raster scan the texel fetch reads a 4-bit
//   INDEX (the right NIBBLE of the fetched byte) from VRAM and gs_stub
//   looks it up in clut_stub -> PSMCT32 color (DECAL). A PASS means the
//   board would SHOW the indexed texture with all 16 palette entries.
//
// Expected (mirrors bake.py clut4_index + clut4_palette):
//   index(x,y) = (x + 2*y) mod 16  (a diagonal ramp through ALL 16).
//   palette: 16 boldly distinct PSMCT32 entries. PCRTC PSMCT32 ->
//            r=color[7:0] g=color[15:8] b=color[23:16].

`timescale 1ns/1ps

module tb_top_psmct32_raster_demo_bram_clut4;

    localparam int H_ACTIVE = 16;
    localparam int V_ACTIVE = 8;
    localparam int TEXW = 8;
    localparam int TEXH = 8;

    logic clk;
    logic 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;
    logic        dma_done_seen;
    logic        frame_seen;
    logic        raster_overflow;
    logic        frame_toggle;
    logic        dma_done_toggle;

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE       (H_ACTIVE),
        .V_ACTIVE       (V_ACTIVE),
        .PSMCT32_SWIZZLE(1'b0)
    ) 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)
    );

    // ----- expected index + 16-entry palette (mirrors bake.py) -----
    function automatic int idx(input int x, input int y);
        return (x + 2*y) % 16;
    endfunction
    function automatic logic [7:0] pal_r(input int i);
        case (i)
            0:  return 8'h00; 1:  return 8'hFF; 2:  return 8'h00; 3:  return 8'h00;
            4:  return 8'hFF; 5:  return 8'hFF; 6:  return 8'h00; 7:  return 8'hFF;
            8:  return 8'h80; 9:  return 8'h00; 10: return 8'h00; 11: return 8'h80;
            12: return 8'h80; 13: return 8'h00; 14: return 8'hC0; default: return 8'h40;
        endcase
    endfunction
    function automatic logic [7:0] pal_g(input int i);
        case (i)
            0:  return 8'h00; 1:  return 8'h00; 2:  return 8'hFF; 3:  return 8'h00;
            4:  return 8'hFF; 5:  return 8'h00; 6:  return 8'hFF; 7:  return 8'hFF;
            8:  return 8'h00; 9:  return 8'h80; 10: return 8'h00; 11: return 8'h80;
            12: return 8'h00; 13: return 8'h80; 14: return 8'h60; default: return 8'h40;
        endcase
    endfunction
    function automatic logic [7:0] pal_b(input int i);
        case (i)
            0:  return 8'h00; 1:  return 8'h00; 2:  return 8'h00; 3:  return 8'hFF;
            4:  return 8'h00; 5:  return 8'hFF; 6:  return 8'hFF; 7:  return 8'hFF;
            8:  return 8'h00; 9:  return 8'h00; 10: return 8'h80; 11: return 8'h00;
            12: return 8'h80; 13: return 8'h80; 14: return 8'h20; default: return 8'h40;
        endcase
    endfunction

    localparam logic [7:0] FLAT_R = 8'h20;
    localparam logic [7:0] FLAT_G = 8'hC0;
    localparam logic [7:0] FLAT_B = 8'h40;

    // ----- frame capture (VRAM_SYNC_READ=1 -> delayed counters) -----
    logic [7:0] cap_r [0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic [7:0] cap_g [0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic [7:0] cap_b [0:V_ACTIVE-1][0:H_ACTIVE-1];
    logic       cap_de[0:V_ACTIVE-1][0:H_ACTIVE-1];
    bit         capture_armed;

    initial begin
        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
        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) begin
        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
    end

    int errors;
    int tex_pixels_ok;
    int distinct_colors;
    initial begin errors = 0; tex_pixels_ok = 0; distinct_colors = 0; end

    initial begin
        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);
        if (dut.clut_ld_busy == 1'b1)         wait (dut.clut_ld_busy == 1'b0);
        if (dut.u_gs.raster_active == 1'b1)   wait (dut.u_gs.raster_active == 1'b0);
        repeat (10) @(posedge clk);

        @(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;

        // --- PSMT4 indexed-textured region (x in [0..7]): each pixel is
        //     palette[index(x,y)] with index = (x+2y) mod 16. ---
        for (int y = 0; y < TEXH; y++) begin
            for (int x = 0; x < TEXW; x++) begin
                logic [7:0] er, eg, eb;
                int i;
                i  = idx(x, y);
                er = pal_r(i); eg = pal_g(i); eb = pal_b(i);
                if (!cap_de[y][x]) begin
                    $error("indexed (%0d,%0d) DE never asserted", x, y);
                    errors = errors + 1;
                end else if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin
                    $error("[indexed] (%0d,%0d) idx=%0d got (%02x,%02x,%02x) exp (%02x,%02x,%02x)",
                           x, y, i, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb);
                    errors = errors + 1;
                end else begin
                    tex_pixels_ok = tex_pixels_ok + 1;
                end
            end
        end

        // --- the indexed region must use MANY distinct palette colors
        //     (proves the 16-entry CLUT indirection + nibble select
        //     produced real variety, not a stuck nibble). The ramp over
        //     an 8x8 region with idx=(x+2y)%16 spans all 16 entries. ---
        begin
            logic [23:0] seen [0:15];
            int n;
            n = 0;
            for (int y = 0; y < TEXH; y++)
                for (int x = 0; x < TEXW; x++) begin
                    logic [23:0] key;
                    bit found;
                    key = {cap_b[y][x], cap_g[y][x], cap_r[y][x]};
                    found = 1'b0;
                    for (int k = 0; k < n; k++) if (seen[k] === key) found = 1'b1;
                    if (!found && n < 16) begin seen[n] = key; n = n + 1; end
                end
            distinct_colors = n;
            if (distinct_colors < 8) begin
                $error("indexed region shows only %0d distinct colors — nibble/CLUT collapsed",
                       distinct_colors);
                errors = errors + 1;
            end
        end

        // --- flat control region (x in [8..15]) ---
        for (int y = 0; y < TEXH; y++) begin
            for (int x = 8; x < 16; x++) begin
                if (!cap_de[y][x]) begin
                    $error("flat (%0d,%0d) DE never asserted", x, y);
                    errors = errors + 1;
                end else if (cap_r[y][x] !== FLAT_R || cap_g[y][x] !== FLAT_G || cap_b[y][x] !== FLAT_B) begin
                    $error("[flat] (%0d,%0d) got (%02x,%02x,%02x) exp (%02x,%02x,%02x)",
                           x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], FLAT_R, FLAT_G, FLAT_B);
                    errors = errors + 1;
                end
            end
        end

        if (!core_halt)      begin $error("core_halt low at end");        errors = errors + 1; end
        if (!dma_done_seen)  begin $error("dma_done_seen never latched"); errors = errors + 1; end
        if (!frame_seen)     begin $error("frame_seen never latched");    errors = errors + 1; end
        if (raster_overflow) begin $error("raster_overflow set");         errors = errors + 1; end

        $display("[tb_top_psmct32_raster_demo_bram_clut4] idx_ok=%0d/64 distinct_colors=%0d raster_emits=%0d errors=%0d",
                 tex_pixels_ok, distinct_colors,
                 dut.u_gs.raster_pixel_emit_count, errors);
        if (errors == 0)
            $display("[tb_top_psmct32_raster_demo_bram_clut4] PASS");
        else
            $display("[tb_top_psmct32_raster_demo_bram_clut4] FAIL");
        $finish;
    end

    initial begin
        #20000000;
        $error("[tb_top_psmct32_raster_demo_bram_clut4] TIMEOUT");
        $finish;
    end

endmodule : tb_top_psmct32_raster_demo_bram_clut4
