// retroDE_ps2 — tb_top_psmct32_sh3_clut_demo (Ch347 authentic board integration, pre-fit gate)
//
// Drives the AUTHENTIC Silent Hill 3 64x64-crop PSMT8 texture + real CLUT through the FULL board path
// (bram top: bootlet -> BITBLT-upload CLUT + PSMT8 texture -> TEX0(CLD=1) fires clut_loader_stub VRAM->CLUT
// load -> DECAL sprite -> PCRTC scanout) and pixel-diffs the SCANOUT RGB against the software reference.
//
// Label (Codex): "authentic 64x64 crop of SH3 PSMT8 indices + real SH3 CLUT rendered on silicon via chosen
// sprite geometry" — NOT full 128x128, NOT a faithful SH3 draw, NOT scene-accurate. DECAL/opaque RGB; the
// real CLUT alpha (~0x04) is preserved in the palette but not blended. Fixtures from gs_make_sh3_fixture.py
// (the crop from gs_extract_sh3_clut.py) — LOCAL/gitignored; NOT in the committable regression (skip-guard).
//
// PASS: every 64x64 scanout pixel RGB == sh3_ref64.mem RGB (= CLUT[crop_index]); >=20 distinct colors.

`timescale 1ns/1ps

module tb_top_psmct32_sh3_clut_demo;
    localparam int H_ACTIVE = 64, V_ACTIVE = 64;

    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, frame_toggle, dma_done_toggle;

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE(H_ACTIVE), .V_ACTIVE(V_ACTIVE), .PSMCT32_SWIZZLE(1'b0),
        .RAM_SIZE_BYTES(8*1024), .VRAM_BYTES(32*1024),           // 64x64 FB(16K)+tex@0x4000(4K)+CLUT(1K)
        .HEARTBEAT_SPLICE_ENABLE(1'b0)   // off: the Ch251/255 heartbeat splice patches EE-RAM qword 115's
                                         // low 32 bits (animation), which collides with our texture payload
    ) 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)
    );

    // software reference (PSMCT32 ABGR = CLUT[crop_idx]); RGB is the authentic-art claim
    logic [31:0] ref_mem [0:H_ACTIVE*V_ACTIVE-1];

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

    int errors, tex_pixels_ok, distinct_colors;

    initial begin
        errors=0; tex_pixels_ok=0; distinct_colors=0;
        ref_mem[0]='x;
        $readmemh("../../data/top_psmct32_raster_demo/sh3_ref64.mem", ref_mem);
        if (ref_mem[0] === 32'bx) begin
            $display("[tb_top_psmct32_sh3_clut_demo] SKIP — sh3_ref64.mem absent (run gs_extract_sh3_clut.py + gs_make_sh3_fixture.py with the local SH3 dump)");
            $finish;
        end

        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;

        // pixel-diff: scanout RGB == CLUT[crop_idx] RGB (= sh3_ref64.mem low 24 bits)
        for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++) begin
            logic [7:0] er,eg,eb; logic [31:0] e;
            e = ref_mem[y*H_ACTIVE+x]; er=e[7:0]; eg=e[15:8]; eb=e[23:16];
            if (!cap_de[y][x]) begin
                if (errors<12) $error("(%0d,%0d) DE never asserted", x, y); errors++;
            end else if (cap_r[y][x]!==er || cap_g[y][x]!==eg || cap_b[y][x]!==eb) begin
                if (errors<12) $error("(%0d,%0d) got (%02x,%02x,%02x) exp (%02x,%02x,%02x)",
                                      x,y, cap_r[y][x],cap_g[y][x],cap_b[y][x], er,eg,eb);
                errors++;
            end else tex_pixels_ok++;
        end
        // variety: a collapsed CLUT would render flat. Count distinct colors (cap up to 32).
        begin
            logic [23:0] seen [0:31]; int n; n=0;
            for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;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<32) begin seen[n]=key; n++; end
            end
            distinct_colors=n;
            if (distinct_colors<20) begin $error("only %0d distinct colors — CLUT indirection collapsed", distinct_colors); errors++; end
        end

        // DIAGNOSTIC: read the FB directly (bypass scanout) to isolate render vs scanout-capture.
        begin
            int fb_errors; fb_errors=0;
            for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++) begin
                logic [31:0] fbw; fbw = dut.u_vram.mem[y*H_ACTIVE+x];
                if (fbw[23:0] !== ref_mem[y*H_ACTIVE+x][23:0]) fb_errors++;
            end
            $display("[tb_top_psmct32_sh3_clut_demo] FB-direct errors=%0d (0 => render OK)", fb_errors);
            if (fb_errors != 0) errors += fb_errors;
        end

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

        $display("[tb_top_psmct32_sh3_clut_demo] px_ok=%0d/%0d distinct_colors=%0d raster_emits=%0d errors=%0d",
                 tex_pixels_ok, H_ACTIVE*V_ACTIVE, distinct_colors, dut.u_gs.raster_pixel_emit_count, errors);
        if (errors==0) $display("[tb_top_psmct32_sh3_clut_demo] PASS");
        else           $display("[tb_top_psmct32_sh3_clut_demo] FAIL");
        $finish;
    end

    initial begin #40000000; $error("[tb_top_psmct32_sh3_clut_demo] TIMEOUT"); $finish; end
endmodule : tb_top_psmct32_sh3_clut_demo
